如何传递std :: unique_ptr?

时间:2012-06-30 20:02:54

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

我第一次尝试使用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的内容作为参考似乎并非本能,这将是参考的参考。其次,因为功能签名变得更大。第三,因为在生成的代码中,需要两个连续的指针间接来达到我的变量。

4 个答案:

答案 0 :(得分:80)

如果希望函数使用指针对象,请传递对它的引用。没有理由将该函数与仅使用某种智能指针联系起来:

bool func(BaseClass& base, int other_arg);

在通话网站上使用operator*

func(*some_unique_ptr, 42);

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

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

答案 1 :(得分:24)

使用std::unique_ptr<T>(除了不必记住明确调用deletedelete[])的优点是它保证指针是nullptr或者它指向(基础)对象的有效实例。在我回答你的问题后,我会回到这个问题,但第一条消息是 DO 使用智能指针来管理动态分配对象的生命周期。

现在,您的问题实际上如何将其与旧代码一起使用

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

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

然后,具有std::shared_ptr<BaseClass> 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!
  5. 正确使用智能指针可以缓解相当可怕的情况3和4,这些情况通常在编译时无法检测到,而且通常只有在程序崩溃或意外事件时才会在运行时遇到。

    将智能指针作为参数传递有两个缺点:您无法更改指向对象的const - 而不进行复制(这会增加shared_ptr的开销,并且unique_ptr)无法使用,而您仍然留有第二个(nullptr)含义。

    我从设计角度将第二种情况标记为(错误)。这是一个关于责任的更微妙的论点。

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

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

答案 2 :(得分:21)

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

bool func(BaseClass& base, int other_arg);

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

所以,即使从unique_ptr中提取引用(甚至是原始指针)似乎也很糟糕(设计方面,编码实践等),但实际上并不是因为它完美根据{{​​1}}设置的所有权规则。然后,当然,还有其他很好的优点,例如干净的语法,对unique_ptr所拥有的对象没有限制,等等。

答案 3 :(得分:0)

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

bool func(BaseClass& base, int other_arg);

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

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

如果班级是指针的唯一所有者,马蒂霍的第二个选择似乎更合理:

func(the_unique_ptr.get(), 10);

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