How to handle a function that is not guaranteed to return anything?

时间:2017-06-15 10:27:23

标签: c++ c++11

I have a class that stores & manages a vector containing a number of objects.

I'm finding myself writing a number of functions similar to the following:

Object* ObjectManager::getObject(std::string name){
  for(auto it = object_store.begin(); it != object_store.end(); ++it){
    if(it->isCalled(name))
      return &(*it)
  }
  return nullptr;
}

I think I would rather return by reference, as here the caller would have to remember to check for null! Is there a way I can change my design to better handle this?

4 个答案:

答案 0 :(得分:8)

Your alternatives are outlined below


Change your API to the following

object_manager.execute_if_has_object("something", [](auto& object) {
    use_object(object);
});

This API is much easier to use, conveys intent perfectly and removes the thought process of error handling, return types, etc from the user's mind


Throw an exception.

Object& ObjectManager::getObject(const std::string& name){
  for(auto& object : object_store){
    if(object.isCalled(name))
      return object;
  }

  // throw an exception
  throw std::runtime_error{"Object not found"};
}

Return a bool, pass the Object by reference and get a copy

bool ObjectManager::getObject(const std::string& name, Object& object_out){
  for(auto& object : object_store){
    if(object.isCalled(name)) {
      object_out = object;
      return true;
    }
  }

  return false;
}

Let the user do the finding

auto iter = std::find(object_store.begin(), object_store.end(), [&name](auto& element) {
    return element.isCalled(name);
}
if (iter != object_store.end()) { ... } 

Also

  1. Pass that string by const reference. When C++17 is available change that const reference to a std::string_view
  2. Use range based for loops in this situation, they are a more readable alternative for what you are doing

答案 1 :(得分:3)

Look at the design of STL (e.g. find function), it is not at all bad to return the iterator your just searched for, and return .end() otherwise.

auto ObjectManager::getObject(std::string name){
  for(auto it = object_store.begin(); it != object_store.end(); ++it){
    if(it->isCalled(name))
      return it;
  }
  return object_store.end();
}

More: Of course object_store.end() may be inaccessible from outside the class but that is not an excuse, because you can do this (note the more slick code also)

auto ObjectManager::getObject(std::string name){
  auto it = object_store.begin();
  while(not it->isCalled(name)) ++it;
  return it;
}
auto ObjectManager::nullObject(){return object_store.end();}

Less code is better code. You can use it like this:

auto result = *om.getObject("pizza"); // search, not check (if you know what you are doing)

or

auto it = om.getObject("pizza");
if(it != om.nullObject() ){ ... do something with *it... }

or

auto it = om.getObject("pizza");
if(it != om.nullObject() ){ ... do something with *it... }
else throw java_like_ridiculous_error("I can't find the object, the universe will collapse and it will jump to another plane of existence");

Of course at this point it is better to call the functions findOject and noposObject and also question why not using directly std::find on the object_store container.

答案 2 :(得分:1)

替代异常或可选的解决方案是实现一个" Null object" - 它可以用作常规对象,但不会做什么"。取决于案例,有时它可以按原样使用,不需要(明确地)检查 - 特别是在忽略"未找到"情况是可以接受的。

(null对象可以是静态全局,因此也可以返回对它的引用)

即使需要检查,也可以实现isNull()方法,对于null对象返回true,对有效对象返回false(或者可以有isValid()方法等)。

示例:

class Object {
public:
    virtual void doSomething();
};

class NullObject: public Object {
public:
    virtual void doSomething() {
        // doing nothing - ignoring the null object
    }
};

class ObjectManager {
public:
    Object& getObject(const std::string& name);
private:
    static NullObject s_nullObject;
};

Object& ObjectManager::getObject(const std::string& name){
  for(auto it = object_store.begin(); it != object_store.end(); ++it){
    if(it->isCalled(name))
      return *it;
  }
  return s_nullObject;
}

ObjectManager mgr;
Object& obj = mgr.getObject(name);
obj.doSomething(); // does nothing if the object is NullObject
                   // (without having to check!)

答案 3 :(得分:1)

我认为您已经正确处理了返回值,并且您当前的解决方案是最佳的。

事实上,您无法避免检查某些内容,以便发现您的查找操作是否成功。如果您抛出异常,那么您的try{}catch{}就是您的支票。如果找不到合法的结果,也不应使用exception。如果您返回bool并使用 out参数,则表明您执行相同工作的情况会更复杂。与返回迭代器相同。 std::optional返回

因此,IMO在返回指针时无法改进,您可以让同样的工作更复杂。