Jaldhar
Jaldhar

Reputation: 279

What to return when a std::map::at goes out_of_range?

In a game I would like to search a map of items and return the one located on a particular square of the board. But what if the square is empty? (The items are not stored in the board structure. Never mind about that for the purposes of this question.) I have the code below, but what should I do to return an "empty" reference?

map<pair<int, int>, Item*> _items;

Item& itemAt(int row, int col) const {
    try {  
        return *_items.at(make_pair(row, col));
    } catch(out_of_range& e) {
        return // what goes here?            
    } 
}

Or is this the wrong approach and I should just use find()?

Upvotes: 8

Views: 2419

Answers (4)

Alexander Oh
Alexander Oh

Reputation: 25641

Actually other people have had the same problem:

  • some of them wrote boost::optional
  • you can return a pair of pointer and return value
  • you can throw an exception
  • you can return a null object

Upvotes: 1

hmjd
hmjd

Reputation: 122001

If it is an error for client code to request an item that does not exist then throw an exception to report the failure and return an Item&.

If it is not an error, as the value_type of the map is already an Item* change the return type to be a Node* and return nullptr to indicate that at item at the requested position does not exist and use map::find().

To avoid lifetime problems, which already exist with Item& return type, consider changing the value_type to be a std::shared_ptr<Item>. If client code has a reference or a raw pointer to a value with the map and that element is removed from the map then the client is left with a dangling pointer/reference. Switching to a std::shared_ptr<Item> avoids this scenario. The itemAt() function would return a std::shared_ptr<Item>. This also has the benefit that the Items in the map do not need to be explicitly deleted.

Upvotes: 3

Roland Levillain
Roland Levillain

Reputation: 456

In this case, using a pointer as a means to represent "zero or one object" is useful:

Item* itemAt(int row, int col) const {
    try {
        return _items.at(make_pair(row, col));
    } catch(out_of_range& e) {
        return nullptr;
    }
}

However, using std::map::find() is probably a faster and cleaner approach.

Upvotes: 9

Andy Prowl
Andy Prowl

Reputation: 126502

If not finding an item is not an error condition in your program, then you should not return a reference (since references cannot be null). Rather, you should return a (non-owning, most likely) pointer, and return nullptr in case the item was not found:

Item* itemAt(int row, int col) const {
    try {  
        return _items.at(make_pair(row, col));
    } catch(out_of_range& e) {
        return nullptr;
    }   
}

On the other hand, if not finding an item is an error, then you can return a reference (when the item is found) and let the exception propagate when the item is not found - the responsibility of handling it would belong to the part of your code that has strategic knowledge on how to handle it:

Item& itemAt(int row, int col) const {
    return *_items.at(make_pair(row, col));
}

Upvotes: 12

Related Questions