智能指针:谁拥有对象?[关闭]


114

C ++全部关于内存所有权(也称为所有权语义)

动态分配的内存块的所有者负责释放该内存。因此,问题实际上变成了谁拥有记忆。

在C ++所有权中,原始指针被包装在其中,因此在一个良好的(IMO)C ++程序中,很少会看到原始指针传递(很少,不是永远不会)(因为原始指针没有推断出的所有权,因此我们可以不知道谁是内存的所有者,因此,如果不仔细阅读文档,就无法知道谁对内存负责。

相反,很少看到原始指针存储在类中,每个原始指针都存储在其自己的智能指针包装器中。(注意:如果您不拥有对象,则不应存储该对象,因为您不知道该对象何时会超出范围并被破坏。)

所以问题是:

  • 人们遇到过哪种所有权语义?
  • 哪些标准类用于实现这些语义?
  • 您觉得它们在什么情况下有用?

让我们为每个答案保留一种类型的语义所有权,以便可以分别对它们进行表决。

摘要:

从概念上讲,智能指针很简单,简单的实现也很容易。我已经看到了许多尝试的实现,但是它们总是以某种偶然使用和示例不明显的方式被破坏。因此,我建议始终使用库中经过良好测试的智能指针,而不要自己动手。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>可能发生指针循环的情况。

用法:仅当循环维护共享引用计数时,用于停止保留对象的循环。


14
?? 有什么问题
Pacerier

9
我只想指出,自从发布此问题以来,auto_ptr已过时,转而支持(现在是标准化的)unique_ptr
Juan Campa,

In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) 可以改写吗?我一点都不明白。
lolololol ol

@lololololol您将句子减半。In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around。RAW指针没有所有权语义。如果您不了解所有者,那么您不知道谁负责删除对象。有几种标准的类用于包装指针(std :: shared_ptr,std :: unique_ptr等),这些指针定义了所有权,因此定义谁负责删除指针。
马丁·约克

1
在C ++ 11 +中,请勿使用auto_ptr!请改用unique_ptr!
瓦尔说莫妮卡

Answers:


20

对我来说,这三种可以满足我的大部分需求:

shared_ptr -参考计数,计数器达到零时释放

weak_ptr-与上述相同,但它是的“从属” shared_ptr,无法取消分配

auto_ptr-当创建和释放发生在同一个函数中时,或者当对象必须被视为只有一个所有者时。当您将一个指针分配给另一个指针时,第二个指针会从第一个“窃取”对象。

我有自己的实现,但也可以在中找到它们Boost

我仍然按引用传递对象(const在可能的情况下),在这种情况下,被调用的方法必须假定对象仅在调用期间处于活动状态。

我使用的另一种指针称为hub_ptr。这是当您有一个必须从嵌套在其中的对象(通常作为虚拟基类)访问的对象时。可以通过将a传递weak_ptr给他们来解决,但是它本身没有a shared_ptr。众所周知,这些对象的寿命不会比他长,因此将hub_ptr传递给它们(它只是常规指针的模板包装器)。


2
而不是创建自己的指针类(hub_ptr),为什么不直接将* this传递给这些对象并让它们存储为引用?既然您甚至承认对象将在拥有类的同时被销毁,所以我不理解跳过这么多箍的意义。
米歇尔,

4
基本上,这是使事情变得清晰的设计合同。当子对象接收到hub_ptr时,它知道指向的对象在子对象的生存期内不会被破坏,并且没有所有权。包含的对象和容器对象都同意一组明确的规则。如果使用裸指针,则规则可以记录下来,但是不会由编译器和代码强制执行。
Fabio Ceconello,09年

1
还要注意,您可以在发布版本中使用#ifdefs将hub_ptr类型化为裸指针,因此开销仅存在于调试版本中。
Fabio Ceconello,2009年

3
请注意,Boost文档与您对scoped_ptr的描述相矛盾。它说是noncopyable,并且所有权不能转让。
亚历克·托马斯

3
@亚历克·托马斯,你是对的。我在考虑auto_ptr并写了scoped_ptr。已更正。
Fabio Ceconello 2012年

23

简单的C ++模型

在大多数模块中,默认情况下,我看到的是假定接收指针获得所有权。实际上,放弃指针所有权的函数/方法非常少见,并在其文档中明确表达了这一事实。

该模型假定用户仅是他/她明确分配的所有者。其他所有内容都会自动清除(在范围出口或通过RAII)。这是一个类似于C的模型,扩展了以下事实:大多数指针归对象所有,这些对象将自动或在需要时(通常是在销毁对象时)释放它们,并且对象的生存期是可预测的(RAII是您的朋友,再次)。

在此模型中,原始指针可以自由循环并且几乎没有危险(但是,如果开发人员足够聪明,他/她将尽可能使用引用代替)。

  • 原始指针
  • std :: auto_ptr
  • boost :: scoped_ptr

智能指针C ++模型

在充满智能指针的代码中,用户可以希望忽略对象的生存期。所有者永远不是用户代码:它是智能指针本身(再次是RAII)。问题在于,将循环引用与引用计数的智能指针混合使用可能是致命的,因此您必须同时处理共享指针和弱指针。因此,您仍然需要考虑所有权(弱指针可能毫无意义,即使它比原始指针的优势在于它可以告诉您)。

  • boost :: shared_ptr
  • 提升:: weak_ptr

结论

无论我描述的模型是什么,除非有例外否则接收指针都不会获得其所有权知道谁拥有谁仍然非常重要。即使对于C ++代码,也大量使用引用和/或智能指针。


10

没有共享所有权。如果这样做,请确保仅使用您无法控制的代码。

这可以解决100%的问题,因为它会迫使您了解一切相互作用的方式。


2
  • 共享所有权
  • boost :: shared_ptr

在多个对象之间共享资源时。boost共享_ptr使用引用计数来确保在每个人都完成任务时取消分配资源。


2

std::tr1::shared_ptr<Blah> 通常是您最好的选择。


2
shared_ptr是最常见的。但是还有更多。每个人都有自己的使用方式以及可以起诉的好坏处。多一点描述会很好。
马丁·约克

如果您使用较旧的编译器,则std :: tr1 :: shared_ptr <blah>所基于的是boost :: shared_ptr <blah>。这是一个足够简单的类,即使最新版本的Boost不支持您的编译器,您也可以将其从Boost中提取并使用。
Branan

2

从boost开始,还有指针容器库。如果仅在对象的上下文中使用对象,则这些对象将比标准的智能指针容器更加有效且易于使用。

在Windows上,有COM指针(IUnknown,IDispatch和friends),以及用于处理它们的各种智能指针(例如,ATL的CComPtr和Visual Studio中基于_com_ptr类由“ import”语句自动生成的智能指针)。)。


1
  • 一位业主
  • boost :: scoped_ptr

当您需要动态分配内存但要确保将其释放在块的每个退出点上时。

我发现它很有用,因为它可以轻松地重新放置和发布,而不必担心泄漏



1

yasper :: ptr是一个轻量级的boost :: shared_ptr,类似。在我的(目前)小型项目中,它运行良好。

在网页http://yasper.sourceforge.net/上,其描述如下:

为什么还要编写另一个C ++智能指针?已经存在几种针对C ++的高质量智能指针实现,最主要的是Boost指针万神殿和Loki的SmartPtr。为了更好地比较智能指针的实现以及何时合适使用它们,请阅读Herb Sutter的The New C ++:Smart(er)Pointers。与其他库的广泛功能相比,Yasper是一种狭focused的引用计数指针。它与Boost的shared_ptr和Loki的RefCounted / AllowConversion策略紧密对应。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允许其他实现不允许的冒险(至今有用)操作(例如,分配给原始指针和手动释放)。注意,只有在知道自己在做什么时才使用这些功能!


1

还有一种经常使用的形式,即单转让拥有人,它是可取的,auto_ptr因为它避免了由auto_ptr分配语义的疯狂破坏引起的问题。

我说的莫过于swapswap可以将任何具有适当功能的类型视为对某些内容的智能引用,这些内容在交换所有权之前将其拥有权转让给相同类型的另一个实例。每个实例保留其身份,但绑定到新内容。这就像一个可安全重新绑定的参考。

(这是一个智能引用,而不是智能指针,因为您无需显式取消引用它即可获取内容。)

这意味着auto_ptr变得不必要了-仅需要填补类型没有好的swap功能的空白。但是所有的std容器都可以。


也许它变得没有必要了(我说scoped_ptr使它的必要性降低了),但是它并没有消失。如果您在堆上分配了某些东西而在删除它之前有人抛出了东西,或者只是忘记了,那么拥有交换功能将根本无济于事。
米歇尔

这正是我在上一段中所说的。
丹尼尔·艾威克

0
  • 一位拥有者:又名复制副本
  • std :: auto_ptr

当对象的创建者想要将所有权显式移交给其他人时。这也是在我提供给您的代码中进行记录的一种方式,并且我不再跟踪它,因此请确保在完成操作后将其删除。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.