MK.
MK.

Reputation: 34597

proper way to return a large object or indicate that it is not found

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

Answers (3)

Corristo
Corristo

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

R Sahu
R Sahu

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

Sergey Kalinichenko
Sergey Kalinichenko

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

Related Questions