如何传递std :: unique_ptr?


83

我正在尝试使用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为引用的东西似乎不是本能,这将是引用的引用。其次,因为功能签名变得更大。第三,因为在生成的代码中,需要两个连续的指针间接访问才能到达我的变量。

Answers:


97

如果要让函数使用pointe,请传递对其的引用。没有理由将函数绑定为仅与某种智能指针一起使用:

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

4
这是很好的,但凸轮你摆脱的std::unique_ptr一个std::vector<std::unique_ptr>说法?
西罗Santilli郝海东冠状病六四事件法轮功

31

使用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是不可引用的,并且可能具有“ nothing”或“ no object”的语义。(
  3. 内存块的地址超出了进程的可寻址空间(取消引用该内存块可能会导致程序崩溃)。(
  4. 可以取消引用但不包含您期望的内存块地址。也许指针被意外修改,现在它指向另一个可写地址(进程中完全其他变量的地址)。写入该内存位置有时会在执行过程中带来很多乐趣,因为只要允许您在该位置写操作系统,操作系统就不会抱怨。(Zoinks!

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

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

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

想象一下当一个函数接收一个nullptr参数作为参数时的含义。它首先必须决定如何处理它:使用“魔术”值代替丢失的对象?完全更改行为并计算其他内容(不需要该对象)?恐慌并抛出异常?此外,当函数通过原始指针接受2个,3个或更多参数时会发生什么?它必须检查它们中的每一个并相应地调整其行为。这在没有任何真正原因的情况下,在输入验证之上增加了一个全新的水平。

呼叫者应该是具有足够的上下文信息来做出这些决定的呼叫者,换句话说,坏消息就越可怕,您就知道越多。另一方面,该函数应仅遵循调用者的承诺,即所指向的内存可以安全地按预期使用。(引用仍然是内存地址,但从概念上讲是对有效性的保证。)


25

我同意马丁纽(Martinho)的观点,但我认为必须指出传递引用的所有权语义。我认为正确的解决方案是在此处使用简单的传递引用:

bool func(BaseClass& base, int other_arg);

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

因此,即使将引用(甚至原始指针)从中拉出似乎是不好的事情(从设计角度,编码实践等)unique_ptr,但这实际上并不是因为它完全符合由设置的所有权规则的unique_ptr。然后,当然还有其他不错的优点,例如语法简洁,不仅仅限制a拥有的对象unique_ptr等等。


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间接费用无法收回。


您是否知道,在大多数情况下,std::unique_ptr开销为零,对吗?
lvella

1
是的,我知道。这就是为什么我告诉他有关std::shared_ptr开销的原因。
Guilherme Ferreira
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.