121

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>在可能发生指针循环的情况下使用 with 。

用法:用于在只有循环维护共享引用计数时停止循环保留对象。

4

11 回答 11

24

简单的 C++ 模型

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

该模型假设用户只是他/她明确分配的内容的所有者。其他所有内容都会自动处理(在范围退出或通过 RAII)。这是一个类似 C 的模型,扩展了大多数指针归对象所有的事实,这些对象将自动或在需要时(主要是在所述对象销毁时)释放它们,并且对象的生命周期是可预测的(RAII 是你的朋友,再次)。

在这个模型中,原始指针是自由循环的,而且大多数情况下并不危险(但如果开发人员足够聪明,他/她将尽可能使用引用来代替)。

  • 原始指针
  • 标准::auto_ptr
  • boost::scoped_ptr

智能指针 C++ 模型

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

  • boost::shared_ptr
  • boost::weak_ptr

结论

无论我描述的模型是什么,除非异常,接收指针并没有接收到它的所有权知道谁拥有谁仍然非常重要。即使对于大量使用引用和/或智能指针的 C++ 代码也是如此。

于 2008-09-18T19:39:20.933 回答
21

对我来说,这 3 种满足了我的大部分需求:

shared_ptr- 引用计数,当计数器达到零时释放

weak_ptr- 与上面相同,但它是 a 的“奴隶” shared_ptr,无法解除分配

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

我对这些有自己的实现,但它们也可以在Boost.

我仍然通过引用传递对象(const只要可能),在这种情况下,被调用的方法必须假定对象仅在调用期间是活动的。

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

于 2008-09-18T17:07:05.887 回答
10

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

这解决了 100% 的问题,因为它迫使你了解一切是如何相互作用的。

于 2008-09-18T17:27:44.370 回答
2
  • 共享所有权
  • boost::shared_ptr

当一个资源在多个对象之间共享时。boost shared_ptr 使用引用计数来确保资源在每个人都完成后被释放。

于 2008-09-18T16:37:35.707 回答
2

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

于 2008-09-18T16:38:51.227 回答
2

从 boost 开始,还有指针容器库。如果您只在其容器的上下文中使用对象,则它们比智能指针的标准容器更有效且更易于使用。

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

于 2008-12-21T12:13:33.663 回答
1
  • 一位业主
  • boost::scoped_ptr

当您需要动态分配内存但希望确保它在块的每个退出点被释放时。

我发现这很有用,因为它可以很容易地重新安装和释放,而不必担心泄漏

于 2008-09-18T17:04:08.763 回答
1

我认为我从来没有能够在我的设计中共享所有权。事实上,我能想到的唯一有效案例是享元模式。

于 2008-09-18T17:13:47.570 回答
1

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

在http://yasper.sourceforge.net/的网页中描述如下:

为什么要编写另一个 C++ 智能指针?已经存在几个用于 C++ 的高质量智能指针实现,最突出的是 Boost 指针 pantheon 和 Loki 的 SmartPtr。为了更好地比较智能指针实现以及何时使用它们,请阅读 Herb Sutter 的 The New C++: Smart(er) Pointers。与其他库的扩展功能相比,Yasper 是一个狭义的引用计数指针。它与 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 允许其他实现不允许的危险(但有用)操作(例如分配给原始指针和手动释放)。请注意,只有在您知道自己在做什么的情况下才使用这些功能!

于 2008-12-21T05:18:48.960 回答
1

还有另一种经常使用的单一可转让所有者形式,它更可取,auto_ptr因为它避免了由于auto_ptr赋值语义的疯狂破坏而导致的问题。

我说的莫过于swap。任何具有合适swap功能的类型都可以被认为是对某些内容的智能引用,它拥有这些内容,直到所有权通过交换它们转移到相同类型的另一个实例时。每个实例都保留其身份,但会绑定到新内容。这就像一个安全的可重新绑定的参考。

(它是智能引用而不是智能指针,因为您不必显式取消引用它来获取内容。)

This means that auto_ptr becomes less necessary - it's only needed to fill the gaps where types don't have a good swap function. But all std containers do.

于 2008-12-21T12:29:49.417 回答
0
  • 一位所有者:也就是复制时删除
  • 标准::auto_ptr

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

于 2008-09-18T16:35:24.293 回答