手动对象所有权 vs 智能指针

4

目前,在我的C++项目中,对象的所有权/删除是手动跟踪的(主要通过注释)。几乎每个堆分配的对象都是使用某种工厂创建的。

例如:

auto b = a->createInstanceOfB(); //a owns b
auto c = b->createInstanceOfC(); //b owns c
//auto k = new K(); //not in the code
...
//b is no longer used..
a->destroyInstanceOfB(b); //destroyInstanceOf calls delete on it

在这种情况下,智能指针会提供哪些好处(如果有)?

“a拥有b”是什么意思?你目前是如何删除东西的? - unquiet mind
通过 a 拥有 b,我指的是 a 创建了 b 并且 a 销毁了 b。 - jameszhao00
目前,删除对象的唯一方法是通过调用所有者的 deleteInstanceOf 函数来删除该对象。 - jameszhao00
3个回答

9

你应该担心的不是创建,而是删除。

使用智能指针(引用计数类型),对象可以被多个其他对象共同拥有,当最后一个引用超出作用域时,对象会自动删除。这样,您就不必再手动删除任何东西了,只有在存在循环依赖关系时才可能泄漏内存,并且您的对象永远不会在背后被删除。

单一所有者类型(std::auto_ptr)也可以解除您的删除职责,但它只允许一次拥有权(虽然所有权可以转移)。这对于您将对象作为指针传递,但仍希望它们在超出作用域时自动清除(以便它们在容器中正常工作,并且在发生异常时堆栈展开按预期工作)非常有用。

无论如何,智能指针使所有权在您的代码中变得明确,不仅对您和您的团队,还对编译器都是如此 - 错误地执行它可能会产生编译器错误或相对容易通过防御性编程捕获的运行时错误。在手动管理内存的代码中,在某个地方出错所有权情况很容易(由于误读注释或错误地假设),而结果是通常很难追踪到的错误 - 您会泄漏内存,覆盖不属于您的内容,程序在任意位置崩溃等。所有这些都有一个共同点,就是出错的情况与有问题的代码部分无关。


假设类A拥有x,但是类B、C、D都存储对x的引用(因此不拥有)。我应该使用什么智能指针来在A中存储x,以及在B中存储x? - jameszhao00
务实的解决方案:对于所有这些对象,使用引用计数智能指针;每当最后一个引用被丢弃时,对象就会被删除。缺点是当所有者(A)下线时,对象会一直保持活动状态,直到所有其他引用也无效为止。然而,另一种选择需要更多的手动工作-使A成为所有者,并确保用户(B、C、D)在A死亡时放弃它们的引用。如何做取决于具体情况。 - tdammers
@james: weak_ptr是将引用传递给用户(而非所有者)的最简单解决方案,因此当x被移除时,它们将无法访问它。 - stefaanv

2
智能指针强制所有权语义-即使在异常情况下,也保证对象将被正确释放。由于安全性,即使它们只表达非常简单的语义,例如std::unique_ptr,您也应该始终使用它们。此外,强制语义的指针减少了对其进行文档记录的需求,并且更少的文档意味着过时或不正确的文档更少-特别是当同一程序的多个部分表达相同的语义时。

最终,智能指针减少了许多错误源,没有理由不使用它们。


1
如果一个对象只被另一个对象所拥有,并随着它的消失而消失,那就没问题了。但仍需确保没有悬空引用,但这不是最困难的情况。
最困难的情况是共享所有权。在这种情况下,您将需要智能指针(或其他东西)来自动确定何时实际删除对象。
请注意,共享所有权并非必需,避免使用它可能会简化事情,使您的产品更加精简。 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接