C++ 的核心是内存所有权 - 又名所有权语义。
动态分配的内存块的所有者有责任释放该内存。 所以问题实际上变成了谁拥有内存。
在 C++ 中,所有权是通过包装在其中的原始指针类型来记录的,因此在一个好的(IMO)C++程序中,它是非常罕见的(罕见,而不是从不< /em>)来查看传递的原始指针(因为原始指针没有推断的所有权,因此我们无法判断谁拥有内存,因此如果不仔细阅读文档,您就无法判断谁负责所有权)。
相反,很少看到原始指针存储在类中,每个原始指针都存储在其自己的智能指针包装器中。 (注意:如果您不拥有某个对象,则不应存储它,因为您不知道它何时会超出范围并被销毁。)
所以问题是:
- 什么类型的所有权语义人们遇到过吗?
- 使用哪些标准类来实现这些语义?
- 您发现它们在什么情况下有用?
让我们为每个答案保留一种类型的语义所有权,以便可以单独对它们进行投票赞成和反对。
简介:
从概念上讲,智能指针很简单,并且简单的实现也很容易。 我见过许多尝试的实现,但它们总是以某种方式被破坏,而这些方式对于临时使用和示例来说并不明显。 因此,我建议始终使用库中经过良好测试的智能指针,而不是自己滚动。 std::auto_ptr
或 Boost 智能指针之一似乎可以满足我的所有需求。
std::auto_ptr
:
单个人拥有该对象。 允许转让所有权。
用法:这允许您定义显示所有权显式转移的接口。
boost::scoped_ptr
单个人拥有该对象。 不允许转让所有权。
用途:用于显示明确的所有权。 对象将被析构函数或显式重置时销毁。
boost::shared_ptr
(std::tr1::shared_ptr
)
多重所有权。 这是一个简单的引用计数指针。 当引用计数达到零时,该对象将被销毁。
用法:当一个对象可以具有多个生命周期且无法在编译时确定的生命周期时。
boost::weak_ptr
:
在可能发生指针循环的情况下与shared_ptr
一起使用。
用法:当只有循环维护共享引用计数时,用于阻止循环保留对象。
C++ is all about memory ownership - aka ownership semantics.
It is the responsibility of the owner of a chunk of dynamically allocated memory to release that memory. So the question really becomes who owns the memory.
In C++ ownership is documented by the type a raw pointer is wrapped inside thus in a good (IMO) C++ program it is very rare (rare, not never) to see raw pointers passed around (as raw pointers have no inferred ownership thus we can not tell who owns the memory and thus without careful reading of the documentation you can't tell who is responsible for ownership).
Conversely, it is rare to see raw pointers stored in a class each raw pointer is stored within its own smart pointer wrapper. (N.B.: If you don't own an object you should not be storing it because you can not know when it will go out of scope and be destroyed.)
So the question:
- What type of ownership semantic have people come across?
- What standard classes are used to implement those semantics?
- In what situations do you find them useful?
Lets keep 1 type of semantic ownership per answer so they can be voted up and down individually.
Summary:
Conceptually, smart pointers are simple and a naive implementation is easy. I have seen many attempted implementations, but invariably they are broken in some way that is not obvious to casual use and examples. Thus I recommend always using well tested smart pointers from a library rather than rolling your own. std::auto_ptr
or one of the Boost smart pointers seem to cover all my needs.
std::auto_ptr<T>
:
Single person owns the object. Transfer of ownership is allowed.
Usage: This allows you to define interfaces that show the explicit transfer of ownership.
boost::scoped_ptr<T>
Single person owns the object. Transfer of ownership is NOT allowed.
Usage: Used to show explicit ownership. Object will be destroyed by destructor or when explicitly reset.
boost::shared_ptr<T>
(std::tr1::shared_ptr<T>
)
Multiple ownership. This is a simple reference counted pointer. When the reference count reaches zero, the object is destroyed.
Usage: When an object can have multiple owers with a lifetime that can not be determined at compile time.
boost::weak_ptr<T>
:
Used with shared_ptr<T>
in situations where a cycle of pointers may happen.
Usage: Used to stop cycles from retaining objects when only the cycle is maintaining a shared refcount.
发布评论
评论(11)
简单的 C++ 模型
在我看到的大多数模块中,默认情况下,假定接收指针不接收所有权。 事实上,放弃指针所有权的函数/方法非常罕见,并且在其文档中明确表达了这一事实。
此模型假设用户仅是他/她明确分配的内容的所有者。 其他所有内容都会自动处置(在范围退出时或通过 RAII)。 这是一个类似 C 的模型,通过以下事实进行扩展:大多数指针由对象拥有,这些对象将自动或在需要时(主要是在所述对象销毁时)释放它们,并且对象的生命周期是可预测的(RAII 是你的朋友,再次)。
在这个模型中,原始指针可以自由循环,并且大多数情况下并不危险(但如果开发人员足够聪明,他/她将尽可能使用引用)。
智能指向 C++ 模型
在充满智能指针的代码中,用户可以希望忽略对象的生命周期。 所有者永远不是用户代码:它是智能指针本身(RAII,再次)。 问题在于,循环引用与引用计数智能指针混合在一起可能是致命的,因此您必须同时处理共享指针和弱指针。 因此,您仍然需要考虑所有权(弱指针很可能指向任何内容,即使它相对于原始指针的优势在于它可以告诉您这一点)。
结论
无论我描述的模型如何,除非异常,接收指针并不是接收其所有权并且它仍然非常重要知道谁拥有谁。 即使对于大量使用引用和/或智能指针的 C++ 代码也是如此。
Simple C++ Model
In most modules I saw, by default, it was assumed that receiving pointers was not receiving ownership. In fact, functions/methods abandoning ownership of a pointer were both very rare and explicitly expressed that fact in their documentation.
This model assumes that the user is owner only of what he/she explicitly allocates. Everything else is automatically disposed of (at scope exit, or through RAII). This is a C-like model, extended by the fact most pointers are owned by objects that will deallocate them automatically or when needed (at said objects destruction, mostly), and that the life duration of objects are predictable (RAII is your friend, again).
In this model, raw pointers are freely circulating and mostly not dangerous (but if the developer is smart enough, he/she will use references instead whenever possible).
Smart Pointed C++ Model
In a code full of smart pointers, the user can hope to ignore the lifetime of objects. The owner is never the user code: It is the smart pointer itself (RAII, again). The problem is that circular references mixed with reference counted smart pointers can be deadly, so you have to deal both with both shared pointers and weak pointers. So you have still ownership to consider (the weak pointer could well point to nothing, even if its advantage over raw pointer is that it can tell you so).
Conclusion
No matter the models I describe, unless exception, receiving a pointer is not receiving its ownership and it is still very important to know who owns who. Even for C++ code heavily using references and/or smart pointers.
对我来说,这 3 种满足了我的大部分需求:
shared_ptr
- 引用计数,计数器达到零时释放weak_ptr
- 与上面相同,但它是“从属”对于shared_ptr
,无法释放auto_ptr
- 当创建和释放发生在同一函数内时,或者当对象必须被视为只有一个所有者时。 当您将一个指针分配给另一个指针时,第二个指针会从第一个指针中“窃取”对象。我有自己的实现,但它们也可以在
Boost
中使用。我仍然通过引用传递对象(尽可能 const),在这种情况下,被调用的方法必须假设该对象仅在调用期间处于活动状态。
我使用另一种指针,称为hub_ptr。 当您有一个对象必须可以从嵌套在其中的对象(通常作为虚拟基类)访问时。 这可以通过向它们传递一个
weak_ptr
来解决,但它本身没有shared_ptr
。 因为它知道这些对象的寿命不会比他长,所以它将一个 hub_ptr 传递给它们(它只是一个常规指针的模板包装器)。For me, these 3 kinds cover most of my needs:
shared_ptr
- reference-counted, deallocation when the counter reaches zeroweak_ptr
- same as above, but it's a 'slave' for ashared_ptr
, can't deallocateauto_ptr
- when the creation and deallocation happen inside the same function, or when the object has to be considered one-owner-only ever. When you assign one pointer to another, the second 'steals' the object from the first.I have my own implementation for these, but they are also available in
Boost
.I still pass objects by reference (
const
whenever possible), in this case the called method must assume the object is alive only during the time of call.There's another kind of pointer that I use that I call hub_ptr. It's when you have an object that must be accessible from objects nested in it (usually as a virtual base class). This could be solved by passing a
weak_ptr
to them, but it doesn't have ashared_ptr
to itself. As it knows these objects wouldn't live longer than him, it passes a hub_ptr to them (it's just a template wrapper to a regular pointer).没有共同所有权。 如果这样做,请确保仅使用您无法控制的代码。
这解决了 100% 的问题,因为它迫使你了解一切是如何相互作用的。
Don't have shared ownership. If you do, make sure it's only with code you don't control.
That solves 100% of the problems, since it forces you to understand how everything interacts.
当资源在多个对象之间共享时。
boost的shared_ptr使用引用计数来确保当每个人都完成时资源被解除分配。
When a resource is shared between multiple objects.
The boost shared_ptr uses reference counting to make sure the resource is de-allocated when everybody is finsihed.
std::tr1::shared_ptr
通常是您最好的选择。std::tr1::shared_ptr<Blah>
is quite often your best bet.在 boost 中,还有 指针容器图书馆。 如果您仅在其容器的上下文中使用对象,那么它们比标准的智能指针容器更高效且更易于使用。
在 Windows 上,有 COM 指针(IUnknown、IDispatch 等)以及用于处理它们的各种智能指针(例如 ATL 的 CComPtr 以及 Visual Studio 中的“import”语句根据 _com_ptr 类)。
From boost, there's also the pointer container library. These are a bit more efficient and easier to use than a standard container of smart pointers, if you'll only be using the objects in the context of their container.
On Windows, there are the COM pointers (IUnknown, IDispatch, and friends), and various smart pointers for handling them (e.g. the ATL's CComPtr and the smart pointers auto-generated by the "import" statement in Visual Studio based on the _com_ptr class).
yasper::ptr 是一个类似 boost::shared_ptr 的轻量级替代方案。 它在我的(目前)小项目中运行良好。
在 http://yasper.sourceforge.net/ 的网页中,描述如下:
yasper::ptr is a lightweight, boost::shared_ptr like alternative. It works well in my (for now) small project.
In the web page at http://yasper.sourceforge.net/ it's described as follows:
我认为我从来没有能够分享我的设计的所有权。 事实上,从我的头脑中,我能想到的唯一有效的案例是享元模式。
I don't think I ever was in a position to have shared ownership in my design. In fact, from the top of my head the only valid case I can think of is Flyweight pattern.
当您需要动态分配内存但希望确保它在块的每个出口点上被释放时。
我发现这很有用,因为它可以轻松地重新安装和释放,而不必担心泄漏
When you need to allocate memory dynamically but want to be sure it gets deallocated on every exit point of the block.
I find this usefull as it can easily be reseated, and released without ever having to worry about a leak
当对象的创建者想要明确地将所有权移交给其他人时。
这也是在我提供给您的代码中记录的一种方式,并且我不再跟踪它,因此请确保在完成后将其删除。
When the creator of the object wants to explicitly hand ownership to somebody else.
This is also a way documenting in the code I am giving this to you and I am no longer tracking it so make sure you delete it when you are finished.
还有另一种经常使用的单一可转让所有者形式,它比
auto_ptr
更可取,因为它避免了auto_ptr
赋值语义的疯狂损坏所引起的问题。我所说的正是
交换
。 任何具有合适的交换
函数的类型都可以被视为对某些内容的智能引用,它拥有这些内容,直到所有权转移到相同类型的另一个实例为止,通过交换它们。 每个实例保留其身份,但绑定到新内容。 它就像一个安全的可重新绑定的参考。(它是一个智能引用而不是智能指针,因为您不必显式取消引用它来获取内容。)
这意味着 auto_ptr 变得不太必要 - 它只需要填补类型没有良好的空白。
交换
功能。 但所有标准容器都是如此。There is another frequently used form of single-transferable-owner, and it is preferable to
auto_ptr
because it avoids the problems caused byauto_ptr
's insane corruption of assignment semantics.I speak of none other than
swap
. Any type with a suitableswap
function can be conceived of as a smart reference to some content, which it owns until such time as ownership is transferred to another instance of the same type, by swapping them. Each instance retains its identity but gets bound to new content. It's like a safely rebindable reference.(It's a smart reference rather than a smart pointer because you don't have to explicitly dereference it to get at the content.)
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.