如何通过std :: unique_ptr?
我正在尝试使用C ++ 11 unique_ptr
; 我正在replace一个属于一个类的项目中的一个多态的原始指针,但是经常传递。
我曾经有这样的function:
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);
因为调用者将不得不处理函数的指针所有权,我不想要的东西。 那么,我的问题最好的解决scheme是什么?
我虽然通过指针作为参考,如下所示:
bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);
但是我觉得这样做很不舒服,首先是因为传递一些已经作为参考的东西似乎是非本能的,这可能是一个参考的参考。 其次,因为函数签名变得更大。 第三,因为在生成的代码中,需要两个连续的指针间接来达到我的variables。
如果你想让这个函数使用指针,那么传递一个对它的引用。 没有任何理由使这个函数只与某种智能指针一起工作:
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);
使用std::unique_ptr<T>
的优点(除了不必记住明确地调用delete
或delete[]
),它保证指针是nullptr
或者它指向(base)对象的有效实例。 回答你的问题之后,我会回到这里,但是第一条消息是使用智能指针来pipe理dynamic分配对象的生命周期。
现在,你的问题实际上是如何与你的旧代码一起使用 。
我的build议是,如果你不想转移或共享所有权,你应该总是传递对象的引用。 像这样声明你的函数(有或没有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 */ }
这意味着任何调用者必须承诺引用是有效的,并且在整个函数体的执行过程中它将继续有效。
这就是为什么我坚信你不应该传递原始指针或引用智能指针的原因。
原始指针只是一个内存地址。 可以有(至less)4个意思之一:
- 所需对象所在的内存块地址。 ( 好 )
- 您可以确定的地址0x0不是可忽略的,并且可能具有“无”或“无对象”的语义。 ( 坏 )
- 在你的进程的可寻址空间之外的一块内存地址(取消引用它将有希望导致你的程序崩溃)。 ( 丑 )
- 一块可以解除引用,但不包含你所期望的内存块的地址。 也许指针被意外修改,现在它指向另一个可写地址(在你的进程中的一个完全的其他variables)。 写入这个内存位置会在执行过程中引起很多乐趣,因为只要允许在那里写入,操作系统就不会抱怨。 ( Zoinks! )
正确使用智能指针可以减轻相当可怕的情况3和4,这些情况通常在编译时无法检测到,而且通常只有在程序崩溃或出现意想不到的情况时才会在运行时遇到。
传递智能指针作为参数有两个缺点:你不能改变被指向的对象的const
而不做复制(这会增加shared_ptr
开销,而unique_ptr
是不可能的),而你仍然留下第二个( nullptr
)的含义。
我从deviseangular度将第二种情况标记为( 坏 )。 这是关于责任的更微妙的论点。
试想一下,当一个函数收到一个nullptr
作为参数时,它意味着什么。 首先必须决定如何处理它:使用“魔法”值来代替丢失的物体? 改变行为,并计算其他东西(不需要对象)? 恐慌并抛出exception? 而且,当函数需要2或3甚至更多的原始指针参数时会发生什么? 它必须检查每一个,并相应地调整其行为。 这在inputvalidation的基础上增加了一个全新的级别,没有真正的原因。
调用者应该是具有足够的上下文信息来做出这些决定的人,换句话说, 坏就是说你知道得越多就越不害怕。 另一方面,这个函数应该只是调用者的承诺,即它所指向的内存是可以安全地按照预期工作的。 (引用仍然是内存地址,但在概念上代表了有效性的承诺。)
我同意Martinho的观点,但是我认为指出通过引用的所有权语义很重要。 我认为正确的解决scheme是在这里使用一个简单的传递引用:
bool func(BaseClass& base, int other_arg);
在C ++中,通过引用传递的一般意义就好像函数的调用者告诉函数“在这里,你可以借用这个对象,使用它,并修改它(如果不是const),但只能用于function体的持续时间“。 这绝对不是与unique_ptr
的所有权规则相冲突的,因为对象只是被短期借用,所以没有实际的所有权转移发生(如果您将汽车借给某人,您是否签署了称号给他?)。
因此,尽pipe从unique_ptr
引用参考(甚至是原始指针)可能看起来很糟糕(devise方面,编码实践等),但实际上并不是因为它完全符合所有权规则集由unique_ptr
。 然后,当然,还有其他很多好处,比如清晰的语法,不限制unique_ptr
拥有的对象,等等。
就个人而言,我避免从指针/智能指针中提取引用。 因为如果指针是nullptr
会发生什么? 如果您将签名更改为:
bool func(BaseClass& base, int other_arg);
您可能必须从空指针解引用中保护您的代码:
if (the_unique_ptr) func(*the_unique_ptr, 10);
如果class级是指针的唯一所有者,那么Martinho的另一个select似乎更合理:
func(the_unique_ptr.get(), 10);
或者,你可以使用std::shared_ptr
。 但是,如果有一个负责delete
实体,则std::shared_ptr
开销不会得到回报。