【问题标题】:What type of object should this function return?这个函数应该返回什么类型的对象?
【发布时间】:2015-01-25 23:38:06
【问题描述】:

考虑这个类:

class Widget
{
    Widget::Widget();
    bool initialize();
}

一个Widget具有以下特点:

  1. 必须调用 initialize() 才能完全构造
  2. initialize() 可能会失败
  3. initialize() 很贵

鉴于此,我将创建封装在始终返回相同 Widget 实例的工厂函数中:

Widget* widget() {
    static auto w = new Widget;
    static auto initialized = false;

    if (!initialized) {
        if (!w->initialize()) {
            return nullptr;
        }
        initialized = true;
    }

    return w;
}

widget() 的返回类型应该是什么?

特别是,我想以某种方式明确返回的Widget 的生命周期将超过任何调用者,但不引用内部实现。

  1. 返回一个原始指针并添加一条注释,指出“返回的指针指向一个具有静态存储持续时间的对象,在程序结束前不会被删除”。这很简单,但不是自我记录。
  2. 返回std::shared_ptr<Widget>。这是自记录的,但我不喜欢它会引入完全不必要的引用计数开销。
  3. 返回一个std::unique_ptr<Widget>,其中包含一个自定义删除器函数,该函数是无操作的。如果调用者将其转换为shared_ptr,我认为这与#2 具有相同的感知问题。

【问题讨论】:

  • 这确实是一个见仁见智的问题,但在这种特殊情况下,我不会担心重新计算开销。如果您的 Widget 类型的创建“成本很高”,那么相比之下,任何“不必要的引用计数开销”都可能是微不足道的。
  • 我们应该忽略这个的多线程不安全性吗? (即使在 C++11 中这也不安全)
  • 我不认为工厂是这个的正确术语。在我看来,工厂是创建对象并将所有权传递给调用者的东西。这更像是一个单例。
  • 我认为 2. 和 3. 根本不是自我记录。如果我调用返回 std::shared_ptr/std::unique_ptr 的函数,我希望获得共享/唯一所有权。
  • @JoshuaJohnson 假设 C++11:虽然静态变量的初始化保证是线程安全的(运行时有责任确保静态初始化只发生一次),但检查 @987654335 的代码@ 不安全。如果widget() 函数几乎同时在两个线程中被调用,则两个线程都可能认为initialized 仍然为假,并且它们都将调用initialize()。特别是如果initialize() make 需要一些时间,这会增加双重初始化的可能性。

标签: c++ c++11 factory smart-pointers lifetime


【解决方案1】:

我投票给:

boost::optional<Widget&> widget() {
    static Widget w; // no reason for this to be a pointer
    static bool initialized = false;

    if (!initialized) {
        if (!w.initialize()) {
            return boost::none;
        }
        initialized = true;
    }

    return w;
}

明确了调用者不以任何方式拥有Widget,调用者delete不担心Widget,调用是否成功也一目了然。

【讨论】:

  • 在没有 Boost 的环境中怎么样?
  • @JoshuaJohnson 你可以从那里复制optional - 它是一个只有标题的库。还有std::experimental::optional。或者您可以编写自己的类,该类有一个可能为空的 Widget* 成员,您必须取消引用才能退出。
  • experimental optional 没有参考支持,我最后检查了。
  • @JoshuaJohnson 如果shared_ptr 被认为是不必要的开销,是否可选引入开销?你可以吗?
【解决方案2】:

在这里做一个原始指针不是正确的吗?它已经表达了限制。它可能会失败(通过返回 nullptr),并且由于它不对指针做出任何承诺,调用者不能安全地导致它被删除。你得到一个原始指针,你不能假设你被允许对指向对象的生命周期做出任何陈述。

【讨论】:

  • 我认为原始指针很好。它记录了调用者不希望获得所有权并且它可能为空的事实。我认为无论选择什么选项,仍然需要通过 cmets 或命名约定明确这是一个 Singleton,因此生命周期是明确的。
【解决方案3】:

Herb Sutter 在这种情况下的建议(http://herbsutter.com/2013/05/30/gotw-90-solution-factories/ 的第 4 项)是返回 optional

函数返回指针可能还有一个额外的原因,即返回 nullptr 以指示无法生成对象。通常,如果我们无法加载小部件,最好抛出异常报告错误。但是,如果无法加载小部件是正常操作并且不应被视为错误,则返回一个可选项,并且如果不需要报告其他类型的错误,则可以通过返回一个空的可选项来很好地沟通.

【讨论】:

  • Herb 不是在说调用者取得被返回对象所有权的情况吗?
  • 啊,是的,好点子。我想知道是否最好删除这个答案?
【解决方案4】:

正如其他人所说,如果工厂只生产一件产品,工厂可能不是正确的术语。它似乎是一个单身人士。

考虑到:

  • 我们将只创建一个 Widget 实例
  • 该实例将在有人第一次请求时构建(如果有)
  • 该实例将一直存在到程序结束,然后应该被销毁
  • 任何人都不应删除实例

我会尝试这样的:

class Widget {
public:
    static Widget& Instance() {
        static Widget w{};
        return w;
    }

private:
    Widget() {
        // Expensive construction
    }
    Widget(const Widget&) = delete; // avoid copy

};

【讨论】:

  • 这很好,但它不能处理初始化可能失败的情况。
【解决方案5】:

为了使生命周期和所有权更清晰,我将使用 Singleton pattern 的约定,并使您的函数成为 Widget 类上的静态 getInstance 函数。

class Widget {
  bool initialize();
 public:
  static Widget* getInstance() {
    static Widget w;
    static bool initialized = false;

    if (!initialized) {
      if (!w.initialize()) {
         return nullptr;
      }
      initialized = true;
    }
    return &w;
  }
};

我认为原始指针返回类型记录了调用者不应获得所有权并且它可能为空的事实。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-09
    • 2019-12-30
    • 2019-03-10
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多