聪明的指针:还是谁拥有你的孩子?
C ++全部关于内存所有权
又名"所有权语义学"
动态分配的内存块的所有者负责释放该内存。因此,问题实际上变成了谁拥有记忆。
在C ++所有权中,RAW指针包裹在其中,因此在一个良好的(IMO)C ++程序中,很少会看到[RARE not NEVER]传递过来的RAW指针(因为RAW指针没有推断出的所有权,因此我们不能告诉谁拥有内存,因此如果不仔细阅读文档,就无法知道谁负责存储。
相反,很少会看到将RAW指针存储在一个类中,每个RAW指针都存储在其自己的SMART指针包装器中。 (<B> N.B .:如果我们不拥有对象,则不应存储该对象,因为我们不知道该对象何时超出范围并被破坏。)
所以问题是:
- 人们遇到过哪种类型的所有权语义?
- 哪些标准类用于实现这些语义?
- 我们觉得它们在什么情况下有用?
让我们为每个答案保留一种语义所有权,以便可以分别对其进行投票和否决
从概念上讲,智能指针很简单,简单的实现也很容易。我已经看到了许多尝试的实现,但是它们总是以某种偶然使用和示例不明显的方式被破坏。因此,我建议始终使用库中经过良好测试的"智能指针",而不要自己动手。 std :: auto_ptr或者boost智能指针之一似乎可以满足我的所有需求。
std :: auto_ptr <T>:
一个人拥有该对象。
但是,所有权转让是允许的。
用法:
这使我们可以定义显示所有权显式转移的接口。
boost :: scoped_ptr <T>
一个人拥有该对象。
不允许转让所有权。
用法:
用于显示明确的所有权。
对象将被析构函数销毁或者在显式重置时销毁。
boost :: shared_ptr <T>(std :: tr1 :: shared_ptr <T>)
多重所有权。
这是一个简单的引用计数指针。当引用计数达到零时,对象将被销毁。
用法:
当对象可以具有多个编译器且其生存期无法在编译时确定的情况下。
boost :: weak_ptr <T>
与shared_ptr <T>一起使用。
在可能发生指针循环的情况下。
用法:
仅当循环维护共享引用计数时,用于停止保留对象的循环。
解决方案
- 一位拥有者:又名复制副本
- std :: auto_ptr
当对象的创建者想要将所有权显式移交给其他人时。
这也是在我提供给代码中进行记录的一种方式,并且我不再跟踪它,因此请确保在完成操作后将其删除。
- 共享所有权
- boost :: shared_ptr
在多个对象之间共享资源时。
在所有人完成任务后,boost shared_ptr使用引用计数来确保资源被取消分配。
std :: tr1 :: shared_ptr <Blah>
通常是我们最好的选择。
- 一位业主
- boost :: scoped_ptr
当我们需要动态分配内存但又要确保将其释放到块的每个出口点时。
我发现它很有用,因为它可以轻松地重新放置和发布,而不必担心泄漏
对我来说,这三种可以满足我的大部分需求:
shared_ptr
引用计数,计数器达到零时释放
weak_ptr
与上面相同,但是它是shared_ptr
的'奴隶',无法取消分配
当创建和释放发生在同一函数中时,或者必须将该对象视为仅拥有者时,就会使用" auto_ptr"。当我们将一个指针分配给另一个指针时,第二个指针会从第一个指针"窃取"该对象。
我有自己的实现,但是它们也可以在Boost
中获得。
我仍然通过引用传递对象(只要有可能,就是" const"),在这种情况下,被调用方法必须假定对象仅在调用期间处于活动状态。
我使用的另一种指针称为hub_ptr。这是当我们有一个必须从嵌套在其中的对象(通常作为虚拟基类)访问的对象时。这可以通过向它们传递一个" weak_ptr"来解决,但是它本身没有一个" shared_ptr"。知道这些对象的寿命不会比他长,因此将hub_ptr传递给它们(它只是常规指针的模板包装器)。
我认为我无法在设计中拥有共同的所有权。实际上,从我的头顶出发,我能想到的唯一有效的案例就是Flyweight模式。
没有共享所有权。如果这样做,请确保仅使用我们无法控制的代码。
这可以解决100%的问题,因为它会迫使我们了解一切相互作用的方式。
简单的C ++模型
在大多数模块中,默认情况下,我看到的是假定接收指针未获得所有权。实际上,放弃指针所有权的函数/方法非常罕见,并且在其文档中明确表达了这一事实。
该模型假定用户仅是他/她明确分配的所有者。其他所有内容都会自动清除(在范围出口处或者通过RAII)。这是一个类似于C的模型,扩展了以下事实:大多数指针归对象所有,这些对象将自动或者在需要时(通常是在销毁对象时)释放它们,并且对象的生存期是可预测的(RAII是朋友,再次)。
在此模型中,原始指针可以自由循环并且几乎没有危险(但是,如果开发人员足够聪明,他/她将尽可能使用引用代替)。
- 原始指针
- std :: auto_ptr
- boost :: scoped_ptr
智能指针C ++模型
在充满智能指针的代码中,用户可以希望忽略对象的生存期。所有者永远不是用户代码:它是智能指针本身(再次是RAII)。问题在于,将循环引用与引用计数的智能指针混合使用可能是致命的,因此我们必须同时处理共享指针和弱指针。因此,我们仍然需要考虑所有权(弱指针可能毫无意义,即使它比原始指针的优势在于它可以告诉我们)。
- boost :: shared_ptr
- 提升:: weak_ptr
总结
无论我描述的模型是什么,除非有例外,否则接收指针都不会获得其所有权,知道谁拥有谁仍然非常重要。即使对于C ++代码,也大量使用引用和/或者智能指针。
yasper :: ptr是一个轻量级的boost :: shared_ptr,类似。在我的(目前)小型项目中,它运行良好。
在网页http://yasper.sourceforge.net/上,其描述如下:
Why write another C++ smart pointer? There already exist several high quality smart pointer implementations for C++, most prominently the Boost pointer pantheon and Loki's SmartPtr. For a good comparison of smart pointer implementations and when their use is appropriate please read Herb Sutter's The New C++: Smart(er) Pointers. In contrast with the expansive features of other libraries, Yasper is a narrowly focused reference counting pointer. It corresponds closely with Boost's shared_ptr and Loki's RefCounted/AllowConversion policies. Yasper allows C++ programmers to forget about memory management without introducing the Boost's large dependencies or having to learn about Loki's complicated policy templates. Philosophy * small (contained in single header) * simple (nothing fancy in the code, easy to understand) * maximum compatibility (drop in replacement for dumb pointers) The last point can be dangerous, since yasper permits risky (yet useful) actions (such as assignment to raw pointers and manual release) disallowed by other implementations. Be careful, only use those features if you know what you're doing!
从boost开始,还有指针容器库。如果仅在对象的上下文中使用对象,则这些对象将比标准的智能指针容器更有效且更易于使用。
在Windows上,有COM指针(IUnknown,IDispatch和friends),以及用于处理它们的各种智能指针(例如,ATL的CComPtr和Visual Studio中基于_com_ptr类由" import"语句自动生成的智能指针)。 )。
还有一种经常使用的形式,可转让拥有者,它比" auto_ptr"更可取,因为它避免了由于" auto_ptr"对分配语义的疯狂破坏而引起的问题。
我只说"交换"。任何具有适当"交换"功能的类型都可以被视为对某些内容的智能引用,这些内容在交换所有权之前将其拥有权转让给相同类型的另一个实例。每个实例保留其身份,但绑定到新内容。这就像一个可安全重新绑定的参考。
(这是一个智能引用,而不是智能指针,因为我们无需显式取消引用它即可获取内容。)
这意味着auto_ptr变得不必要了,它只需要填补类型没有良好的swap
函数的空白。但是所有的std容器都可以。