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?

+3


source to share


3 answers


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
}

      

+4


source


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;
}

      

+1


source


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
}

      

0


source







All Articles