如何处理不保证返回任何内容的函数

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

本文关键字:返回 任何内 函数 何处理 处理      更新时间:2023-10-16

我有一个类,用于存储和管理包含许多对象的向量。

我发现自己正在编写许多类似于以下内容的函数:

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

我想我宁愿通过引用返回,因为在这里调用者必须记住检查 null!有没有办法改变我的设计以更好地处理这个问题?

您的替代方案概述如下


将 API 更改为以下内容

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

此 API 更易于使用,完美地传达了意图,并从用户的脑海中消除了错误处理、返回类型等的思维过程


引发异常。

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

返回一个布尔值,通过引用传递Object并获取副本

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

让用户进行查找

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

  1. 通过常量引用传递该字符串。 当 C++17 可用时,更改对std::string_view的常量引用
  2. 在这种情况下使用基于范围的 for 循环,它们是您正在执行的操作的更具可读性的替代方案

看看STL的设计(例如 find函数(,返回您刚刚搜索的迭代器并返回.end()否则一点也不坏。

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

更多:当然object_store.end()可能无法从类外访问,但这不是借口,因为您可以这样做(请注意更流畅的代码(

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

代码越少越好。你可以像这样使用它:

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

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

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");

当然,在这一点上,最好调用函数findOjectnoposObject,并质疑为什么不在object_store容器上使用直接std::find

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

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

因此,IMO 您无法在返回指针进行改进,您只能使相同的工作更加复杂。

异常或可选的替代方案是实现一个"Null 对象" - 它可以用作常规对象,但"什么都不做"。根据具体情况,有时它可以按原样使用,不需要(明确(检查 - 特别是在忽略"未找到"情况是可以接受的情况下。

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

即使需要检查,也可以实现isNull()方法,该方法为空对象返回 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!)