智能指针:或者谁拥有你的宝贝?[关闭]
-
01-07-2019 - |
题
C++ 是关于内存所有权的
又名“所有权语义"
动态分配的内存块的所有者有责任释放该内存。所以问题实际上变成了谁拥有内存。
在 C++ 中,所有权是通过 RAW 指针包装在其中的类型来记录的,因此在一个好的 (IMO) C++ 程序中,很少会看到 RAW 指针被传递(因为 RAW 指针没有推断出的所有权,因此我们不能告诉谁拥有内存,因此如果不仔细阅读文档,您就无法告诉谁负责所有权)。
相反,很少看到 RAW 指针存储在类中,每个 RAW 指针都存储在其自己的 SMART 指针包装器中。(注意: 如果您不拥有某个对象,则不应存储它,因为您不知道它何时会超出范围并被销毁。)
所以问题是:
- 人们遇到过什么类型的所有权语义?
- 使用哪些标准类来实现这些语义?
- 您认为它们在什么情况下有用?
让我们为每个答案保留一种类型的语义所有权,以便可以单独对它们进行投票赞成和反对
概括:
从概念上讲,智能指针很简单,并且简单的实现也很容易。我见过许多尝试的实现,但它们总是以某种方式被破坏,而这些方式对于临时使用和示例来说并不明显。因此,我建议始终使用库中经过良好测试的“智能指针”,而不是自己推出。std::auto_ptr 或 boost 智能指针之一似乎满足了我的所有需求。
std::auto_ptr<T>:
单个人拥有该对象。
但允许转让所有权。
用法:
======
这允许您定义显示所有权显式转移的接口。
boost::scoped_ptr<T>
单个人拥有该对象。
不允许转让所有权。
用法:
======
用于显示明确的所有权。
对象将被析构函数或显式重置时销毁。
boost::shared_ptr<T> (std::tr1::shared_ptr<T>)
多重所有权。
这是一个简单的引用计数指针。当引用计数达到零时,对象将被销毁。
用法:
======
当对象可以有多个权限时,其生命周期无法在编译时确定。
升压::weak_ptr<T>
与shared_ptr<T> 一起使用。
在可能发生指针循环的情况下。
用法:
======
当只有循环维护共享引用计数时,用于阻止循环保留对象。
解决方案
对我来说,这3种已经满足了我的大部分需求:
shared_ptr
- 引用计数,当计数器达到零时释放
weak_ptr
- 与上面相同,但它是一个“奴隶” shared_ptr
, 无法解除分配
auto_ptr
- 当创建和释放发生在同一个函数内时,或者当对象必须被视为只有一个所有者时。当您将一个指针分配给另一个指针时,第二个指针会从第一个指针中“窃取”对象。
我有自己的实现,但它们也可以在 Boost
.
我仍然通过引用传递对象(const
尽可能),在这种情况下,被调用的方法必须假设对象仅在调用期间处于活动状态。
我使用另一种指针,我称之为 集线器指针. 。当您有一个对象必须可以从嵌套在其中的对象(通常作为虚拟基类)访问时。这可以通过传递一个来解决 weak_ptr
对他们来说,但它没有 shared_ptr
对自己。因为它知道这些对象的寿命不会比他长,所以它将一个 hub_ptr 传递给它们(它只是一个常规指针的模板包装器)。
其他提示
简单的 C++ 模型
在我看到的大多数模块中,默认情况下,假设接收指针是 不是 接收所有权。事实上,放弃指针所有权的函数/方法非常罕见,并且在其文档中明确表达了这一事实。
该模型假设用户仅是他/她明确分配的内容的所有者. 。其他所有内容都会自动处置(在范围退出时或通过 RAII)。这是一个类似 C 的模型,通过以下事实进行扩展:大多数指针由对象拥有,这些对象将自动或在需要时(主要是在所述对象销毁时)释放它们,并且对象的生命周期是可预测的(RAII 是你的朋友,再次)。
在这个模型中,原始指针可以自由循环,并且大多数情况下并不危险(但如果开发人员足够聪明,他/她将尽可能使用引用)。
- 原始指针
- std::auto_ptr
- 升压::scoped_ptr
智能指向 C++ 模型
在充满智能指针的代码中,用户可以希望忽略对象的生命周期。所有者永远不是用户代码:它是智能指针本身(又是 RAII)。 问题在于循环引用与引用计数智能指针混合可能是致命的, ,所以你必须同时处理共享指针和弱指针。因此,您仍然需要考虑所有权(弱指针很可能指向任何内容,即使它相对于原始指针的优势在于它可以告诉您这一点)。
- 升压::shared_ptr
- 提升::weak_ptr
结论
无论我描述什么模型, 除非有异常,否则接收指针是 不是 获得其所有权 和 知道谁拥有谁仍然非常重要. 。即使对于大量使用引用和/或智能指针的 C++ 代码也是如此。
没有共同所有权。如果这样做,请确保仅使用您无法控制的代码。
这解决了 100% 的问题,因为它迫使你了解一切是如何相互作用的。
- 共享所有权
- 升压::shared_ptr
当资源在多个对象之间共享时。boost的shared_ptr使用引用计数来确保当每个人都完成时资源被解除分配。
std::tr1::shared_ptr<Blah>
通常是您最好的选择。
- 一位业主
- 升压::scoped_ptr
当您需要动态分配内存但希望确保在块的每个出口点释放内存时。
我发现这很有用,因为它可以轻松地重新安装和释放,而不必担心泄漏
我认为我从来没有能够分享我的设计的所有权。事实上,从我的头脑中,我能想到的唯一有效的案例是享元模式。
yasper::ptr 是一个类似 boost::shared_ptr 的轻量级替代方案。它在我的(目前)小项目中运行良好。
在网页中 http://yasper.sourceforge.net/ 描述如下:
为什么要再写一个 C++ 智能指针呢?C ++已经存在了几种高质量的智能指针实现,最突出的是Boost Pointer Pantheon和Loki的SmartPtr。要对智能指针实现进行良好的比较,并且当它们使用时,请阅读Herb Sutter的新C ++:智能(呃)指针。与其他库的宽敞特征相反,Yasper是一个狭窄的参考计数指针。它与Boost的共享_ptr和Loki的重新计算/允许转换策略密切相关。Yasper允许C ++程序员忘记内存管理,而无需引入Boost的大依赖性或必须了解Loki的复杂政策模板。哲学
* small (contained in single header) * simple (nothing fancy in the code, easy to understand) * maximum compatibility (drop in replacement for dumb pointers)
最后一点可能是危险的,因为Yasper允许其他实施不允许使用风险(但有用的)动作(例如对原始指针和手动释放的分配)。请小心,只有知道自己在做什么,仅使用这些功能!
还有另一种常用的单一可转让所有者形式,最好是 auto_ptr
因为它避免了由以下原因引起的问题 auto_ptr
赋值语义的疯狂损坏。
我说的无非是 swap
. 。具有合适的任何类型 swap
函数可以被理解为 智能参考 到某些内容,它拥有这些内容,直到所有权通过交换它们转移到相同类型的另一个实例。每个实例保留其身份,但绑定到新内容。它就像一个安全的可重新绑定的参考。
(这是一个智能引用而不是智能指针,因为您不必显式取消引用它来获取内容。)
这意味着 auto_ptr 变得不再那么必要了 - 它只需要填补类型没有良好的空白 swap
功能。但所有标准容器都是如此。
- 业主一名:又名复制删除
- std::auto_ptr
当对象的创建者想要明确地将所有权移交给其他人时。这也是在我提供给您的代码中记录的一种方式,并且我不再跟踪它,因此请确保在完成后将其删除。