Correct way to return a large object or indicate that it was not found
What is the idiomatic C ++ way? I have a method that 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 instantiate a new LOB and place it in the container. I do not want it. I don't want to throw an exception either. I want the return value to signal that the object was not found (since this is more or less a normal situation). Therefore my parameters are pointers or optional. Pointer I understand and love, but it looks like C ++ doesn't want to use pointers anymore. So on to the alternatives. I'll return the option and then the caller looks like this:
std::optional<LargeObject> oresult = lookupLargeObject(42);
LargeObject result;
if (oresult) {
result = *oresult;
} else {
// deal with it
}
It is right? It looks like crappy to me because it seems like I am creating 2 copies of LargeObject here? Once when returning an optional and once when retrieving from an optional result. Should there be a better way?
source to share
Since you don't want to return a pointer, but you also don't want to throw an exception, and you probably want to use reference semantics, the simplest is to return std::optional<std::reference_wrapper<LargeObject>>
.
The code will 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);
}
}
Since C ++ 17, you can even declare a variable iter
inside if
-condition.
Calling the search function and using the reference then looks like this (here with a 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
}
source to share
There are two approaches that do not require the use of pointers - using a sentinel object and getting a reference instead of returning it.
The first approach is based on assigning a special LargeObject
"invalid" instance: let's say by creating a member function called isValid
and returning false
for that object. lookupLargeObject
will return this object to indicate that no real object was found:
LargeObject& lookupLargeObject(int id) {
if (largeObjects.find(id) == largeObjects.end()) {
static LargeObject notFound(false);
return notFound;
}
return largeObjects[id];
}
The second approach is passing in the link rather than getting it back:
bool lookupLargeObject(int id, LargeObject& res) {
if (largeObjects.find(id) == largeObjects.end()) {
return false;
}
res = largeObjects[id];
return true;
}
source to share
If the default built is LargeObject
undesirable from lookupLargeObject
, regardless of whether it is expensive or lacks semantic meaning, you can use a member function std:map::at
.
LargeObject& lookupLargeObject(int id) {
return largeObjects.at(id);
}
If you want to live using blocks of code if-else
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 the client code could be:
LargeObject* result = lookupLargeObject(42);
if (result) {
// Use result
} else {
// deal with it
}
source to share