Reputation: 34597
What is the idiomatic C++ way of doing this? I have a method which looks like this:
LargeObject& lookupLargeObject(int id) {
return largeObjects[id];
}
This is wrong, because if you call this with a non-existent id it will create a new instance of large object and put it into the container. I don't want that. I don't want to throw an exception either. I want the return value to signal that object wasn't found (as it is a more or less normal situation). So my options are either a pointer or an optional. Pointer I understand and like, but it feels like C++ doesn't want to me use pointers any more. So on to optionals. I will return an optional and then the caller looks like this:
std::optional<LargeObject> oresult = lookupLargeObject(42);
LargeObject result;
if (oresult) {
result = *oresult;
} else {
// deal with it
}
Is this correct? It feels kind of crappy because it seems that I'm creating 2 copies of the LargeObject here? Once when returning the optional and once when extracting it from optional into result. Gotta be a better way?
Upvotes: 3
Views: 262
Reputation: 5520
Since you don't want to return a pointer, but also don't want to throw an exception, and you presumably want reference semantics, the easiest thing to do is to return a std::optional<std::reference_wrapper<LargeObject>>
.
The code would look like this:
std::optional<std::reference_wrapper<LargeObject>> lookupLargeObject(int id) {
auto iter = largeObjects.find(id);
if (iter == largeObjects.end()) {
return std::nullopt;
} else {
return std::ref(iter->second);
}
}
With C++17 you can even declare the iter
variable inside the if
-condition.
Calling the lookup function and using the reference then looks like this (here with variable declaration inside if
-condition):
if (auto const lookup_result = lookupLargeObject(42); lookup_result) {
auto& large_object = lookup_result.value().get();
// do something with large_obj
} else {
// deal with it
}
Upvotes: 5
Reputation: 206747
If default constructed LargeObject
is unwanted from lookupLargeObject
, regardless of whether it is expensive or it does not make semantic sense, you can use the std:map::at
member function.
LargeObject& lookupLargeObject(int id) {
return largeObjects.at(id);
}
If you are willing to live with use of if-else
blocks of code in the calling function, I would change the return type of the function to LargeObject*
.
LargeObject* lookupLargeObject(int id) {
auto it = largeObjects.find(id);
if ( it == largeObjects.end() )
{
return nullptr;
}
return &(it->second);
}
Then, client code can be:
LargeObject* result = lookupLargeObject(42);
if (result) {
// Use result
} else {
// deal with it
}
Upvotes: 0
Reputation: 727097
There are two approaches that do not require use of pointers - using a sentinel object, and receiving a reference, instead of returning it.
The first approach relies on designating a special instance of LargeObject
an "invalid" one - say, by making a member function called isValid
, and returning false
for that object. lookupLargeObject
would return that object to indicate that the real object was not found:
LargeObject& lookupLargeObject(int id) {
if (largeObjects.find(id) == largeObjects.end()) {
static LargeObject notFound(false);
return notFound;
}
return largeObjects[id];
}
The second approach passes a reference, rather than receiving it back:
bool lookupLargeObject(int id, LargeObject& res) {
if (largeObjects.find(id) == largeObjects.end()) {
return false;
}
res = largeObjects[id];
return true;
}
Upvotes: 1