【问题标题】:How to pass std::unique_ptr around?如何传递 std::unique_ptr ?
【发布时间】:2012-07-01 21:32:58
【问题描述】:

我第一次尝试使用 C++11 unique_ptr;我在我的一个项目中替换了一个多态原始指针,它归一个类所有,但传递的频率很高。

我曾经有过这样的功能:

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

但我很快意识到我无法切换到:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

因为调用者必须处理函数的指针所有权,我不想这样做。那么,我的问题的最佳解决方案是什么?

我虽然将指针作为参考传递,像这样:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

但是这样做我感到很不舒服,首先因为传递已经输入为_ptr 的东西作为参考似乎不是本能的,什么是参考的参考。其次,因为函数签名变得更大。第三,因为在生成的代码中,需要两个连续的指针间接访问我的变量。

【问题讨论】:

    标签: c++ c++11 unique-ptr


    【解决方案1】:

    如果您希望函数使用指针,请传递对它的引用。没有理由将函数绑定为仅使用某种智能指针:

    bool func(BaseClass& base, int other_arg);
    

    在呼叫站点使用operator*

    func(*some_unique_ptr, 42);
    

    或者,如果base 参数允许为空,则保持签名不变,并使用get() 成员函数:

    bool func(BaseClass* base, int other_arg);
    func(some_unique_ptr.get(), 42);
    

    【讨论】:

    • 这很好,但是您可以摆脱std::unique_ptrstd::vector&lt;std::unique_ptr&gt; 参数吗?
    【解决方案2】:

    使用std::unique_ptr&lt;T&gt; 的优点(除了不必记得显式调用deletedelete[])是它保证一个指针要么是nullptr 要么它指向一个有效的实例(基础)对象。回答完你的问题后,我会回到这个问题,但第一条消息是DO使用智能指针来管理动态分配对象的生命周期。

    现在,您的问题实际上是如何在旧代码中使用它

    我的建议是,如果您不想转让或共享所有权,您应该始终传递对该对象的引用。像这样声明你的函数(有或没有const 限定符,根据需要):

    bool func(BaseClass& ref, int other_arg) { ... }
    

    然后,具有std::shared_ptr&lt;BaseClass&gt; ptr 的调用者将处理nullptr 的情况,或者它会要求bool func(...) 计算结果:

    if (ptr) {
      result = func(*ptr, some_int);
    } else {
      /* the object was, for some reason, either not created or destroyed */
    }
    

    这意味着任何调用者都必须承诺引用是有效的,并且在函数体的整个执行过程中它将继续有效。


    这就是我坚信您应该传递原始指针或对智能指针的引用的原因。

    原始指针只是一个内存地址。可以具有(至少)4 种含义之一:

    1. 所需对象所在的内存块的地址。 (好的
    2. 您可以确定的地址 0x0 是不可取消引用的,并且可能具有“无”或“无对象”的语义。 (坏的
    3. 一个内存块的地址,它在您的进程的可寻址空间之外(取消引用它可能会导致您的程序崩溃)。 (丑陋的
    4. 可以取消引用但不包含您期望的内存块的地址。也许指针被意外修改了,现在它指向另一个可写地址(在您的进程中完全是另一个变量)。写入这个内存位置有时会在执行过程中产生很多乐趣,因为只要你被允许在那里写,操作系统就不会抱怨。 (Zoinks!

    正确使用智能指针可以缓解相当可怕的情况 3 和 4,这些情况通常在编译时无法检测到,并且您通常只会在运行时遇到程序崩溃或发生意外情况时。

    将智能指针作为参数传递有两个缺点:您不能在不复制的情况下更改 pointed 对象的 const-ness(这会增加 shared_ptr 的开销,而对于 @ 则不可能) 987654333@),你还剩下第二个 (nullptr) 的意思。

    从设计的角度来看,我将第二种情况标记为(不好的)。这是关于责任的更微妙的论点。

    想象一下当一个函数接收一个nullptr 作为它的参数时这意味着什么。它首先必须决定如何处理它:使用“神奇”值代替丢失的对象?完全改变行为并计算其他东西(不需要对象)?恐慌并抛出异常?此外,当函数通过原始指针获取 2、3 甚至更多参数时会发生什么?它必须检查它们中的每一个并相应地调整其行为。这无缘无故地在输入验证之上增加了一个全新的级别。

    调用者应该是具有足够上下文信息来做出这些决定的人,或者换句话说,坏人你知道的越多,就越不可怕。另一方面,该函数应该只接受调用者的承诺,即它所指向的内存可以安全地按预期使用。 (引用仍然是内存地址,但在概念上代表了有效性的承诺。)

    【讨论】:

      【解决方案3】:

      我同意 Martinho 的观点,但我认为指出传递引用的所有权语义很重要。我认为正确的解决方案是在这里使用简单的传递引用:

      bool func(BaseClass& base, int other_arg);
      

      在 C++ 中传递引用的普遍接受的含义就像函数的调用者告诉函数“在这里,你可以借用这个对象,使用它并修改它(如果不是 const),但是仅在函数体的持续时间内。”这绝不与unique_ptr 的所有权规则相冲突,因为该对象只是在短时间内被借用,并没有发生实际的所有权转移(如果您将汽车借给某人,您会把标题签给他?)。

      因此,即使从unique_ptr 中提取引用(甚至是原始指针)看起来很糟糕(设计方面、编码实践等),但实际上并不是因为它完全符合使用unique_ptr 设置的所有权规则。当然,还有其他不错的优点,比如简洁的语法、不限制只属于 unique_ptr 的对象等等。

      【讨论】:

        【解决方案4】:

        就个人而言,我避免从指针/智能指针中提取引用。因为如果指针是nullptr 会发生什么?如果您将签名更改为:

        bool func(BaseClass& base, int other_arg);
        

        您可能必须保护您的代码免受空指针取消引用:

        if (the_unique_ptr)
          func(*the_unique_ptr, 10);
        

        如果类是指针的唯一所有者,Martinho 的第二个替代方案似乎更合理:

        func(the_unique_ptr.get(), 10);
        

        或者,您可以使用std::shared_ptr。但是,如果只有一个实体负责 deletestd::shared_ptr 的开销就不会得到回报。

        【讨论】:

        • 您是否知道,在大多数情况下,std::unique_ptr 的开销为零,对吧?
        • 是的,我知道。这就是我告诉他std::shared_ptr 开销的原因。
        猜你喜欢
        • 1970-01-01
        • 2015-09-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-25
        • 2016-03-31
        • 2013-01-16
        • 1970-01-01
        相关资源
        最近更新 更多