使用std::unique_ptr<T> 的优点(除了不必记得显式调用delete 或delete[])是它保证一个指针要么是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 种含义之一:
- 所需对象所在的内存块的地址。 (好的)
- 您可以确定的地址 0x0 是不可取消引用的,并且可能具有“无”或“无对象”的语义。 (坏的)
- 一个内存块的地址,它在您的进程的可寻址空间之外(取消引用它可能会导致您的程序崩溃)。 (丑陋的)
- 可以取消引用但不包含您期望的内存块的地址。也许指针被意外修改了,现在它指向另一个可写地址(在您的进程中完全是另一个变量)。写入这个内存位置有时会在执行过程中产生很多乐趣,因为只要你被允许在那里写,操作系统就不会抱怨。 (Zoinks!)
正确使用智能指针可以缓解相当可怕的情况 3 和 4,这些情况通常在编译时无法检测到,并且您通常只会在运行时遇到程序崩溃或发生意外情况时。
将智能指针作为参数传递有两个缺点:您不能在不复制的情况下更改 pointed 对象的 const-ness(这会增加 shared_ptr 的开销,而对于 @ 则不可能) 987654333@),你还剩下第二个 (nullptr) 的意思。
从设计的角度来看,我将第二种情况标记为(不好的)。这是关于责任的更微妙的论点。
想象一下当一个函数接收一个nullptr 作为它的参数时这意味着什么。它首先必须决定如何处理它:使用“神奇”值代替丢失的对象?完全改变行为并计算其他东西(不需要对象)?恐慌并抛出异常?此外,当函数通过原始指针获取 2、3 甚至更多参数时会发生什么?它必须检查它们中的每一个并相应地调整其行为。这无缘无故地在输入验证之上增加了一个全新的级别。
调用者应该是具有足够上下文信息来做出这些决定的人,或者换句话说,坏人你知道的越多,就越不可怕。另一方面,该函数应该只接受调用者的承诺,即它所指向的内存可以安全地按预期使用。 (引用仍然是内存地址,但在概念上代表了有效性的承诺。)