92

我第一次尝试使用 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

4 回答 4

106

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

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);
于 2012-06-30T20:07:14.350 回答
40

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

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

我的建议是,如果您不想转让或共享所有权,则应始终传递对该对象的引用。像这样声明你的函数(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!

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

将智能指针作为参数传递有两个缺点:如果不进行复制,就无法更改指向const对象的-ness (这会增加开销并且对于 是不可能的),并且您仍然保留第二个 ( ) 含义。shared_ptrunique_ptrnullptr

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

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

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

于 2014-06-18T15:37:33.303 回答
29

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

bool func(BaseClass& base, int other_arg);

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

因此,即使将引用(甚至原始指针)从unique_ptr. unique_ptr. _ 然后,当然,还有其他不错的优点,比如干净的语法,不限制只属于 a 的对象unique_ptr,等等。

于 2012-07-01T00:41:58.897 回答
0

就个人而言,我避免从指针/智能指针中提取引用。因为如果指针是 会发生什么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. 但是,如果只有一个实体负责delete,则std::shared_ptr开销不会得到回报。

于 2017-03-10T22:23:55.063 回答