C++ std::tr1::shared_ptr 是如何实现的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9200664/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How is the std::tr1::shared_ptr implemented?
提问by purepureluck
I've been thinking about using shared pointers, and I know how to implement one myself--Don't want to do it, so I'm trying std::tr1::shared_ptr
,and I have couple of questions...
我一直在考虑使用共享指针,我知道如何自己实现一个——不想这样做,所以我正在尝试std::tr1::shared_ptr
,我有几个问题......
How is the reference counting implemented? Does it use a doubly linked list? (Btw, I've already googled, but I can't find anything reliable.)
引用计数是如何实现的?它是否使用双向链表?(顺便说一句,我已经用谷歌搜索过了,但我找不到任何可靠的东西。)
Are there any pitfalls for using the std::tr1::shared_ptr
?
使用 有任何陷阱std::tr1::shared_ptr
吗?
回答by Emilio Garavaglia
shared_ptr
must manage a reference counter and the carrying of a deleter functor that is deduced by the type of the object given at initialization.
shared_ptr
必须管理一个引用计数器和一个删除器函子的携带,它是由初始化时给出的对象类型推导出来的。
The shared_ptr
class typically hosts two members: a T*
(that is returned by operator->
and dereferenced in operator*
) and a aux*
where aux
is a inner abstract class that contains:
所述shared_ptr
类通常承载两个成员:一个T*
(由返回operator->
并在解除引用operator*
)和一个aux*
其中aux
是包含内抽象类:
- a counter (incremented / decremented upon copy-assign / destroy)
- whatever is needed to make increment / decrement atomic (not needed if specific platform atomic INC/DEC is available)
- an abstract
virtual destroy()=0;
- a virtual destructor.
- 一个计数器(在复制分配/销毁时增加/减少)
- 使增量/减量原子化所需的任何内容(如果特定平台原子 INC/DEC 可用,则不需要)
- 摘要
virtual destroy()=0;
- 一个虚拟析构函数。
Such aux
class (the actual name depends on the implementation) is derived by a family of templatized classes (parametrized on the type given by the explicit constructor, say U
derived from T
), that add:
这样的aux
类(实际名称取决于实现)由一系列模板化类(参数化由显式构造函数给出的类型,例如U
派生自T
)派生,并添加:
- a pointer to the object (same as
T*
, but with the actual type: this is needed to properly manage all the cases ofT
being a base for whateverU
having multipleT
in the derivation hierarchy) - a copy of the
deletor
object given as deletion policy to the explicit constructor (or the defaultdeletor
just doing deletep
, wherep
is theU*
above) - the override of the destroy method, calling the deleter functor.
- 指向对象的指针(与 相同
T*
,但具有实际类型:这是正确管理作为派生层次结构中具有多个的T
任何基础的所有情况所必需的)U
T
deletor
作为删除策略提供给显式构造函数的对象的副本(或默认deletor
只是执行 deletep
,上面p
是哪里U*
)- destroy 方法的覆盖,调用删除器函子。
A simplified sketch can be this one:
一个简化的草图可以是这样的:
template<class T>
class shared_ptr
{
struct aux
{
unsigned count;
aux() :count(1) {}
virtual void destroy()=0;
virtual ~aux() {} //must be polymorphic
};
template<class U, class Deleter>
struct auximpl: public aux
{
U* p;
Deleter d;
auximpl(U* pu, Deleter x) :p(pu), d(x) {}
virtual void destroy() { d(p); }
};
template<class U>
struct default_deleter
{
void operator()(U* p) const { delete p; }
};
aux* pa;
T* pt;
void inc() { if(pa) interlocked_inc(pa->count); }
void dec()
{
if(pa && !interlocked_dec(pa->count))
{ pa->destroy(); delete pa; }
}
public:
shared_ptr() :pa(), pt() {}
template<class U, class Deleter>
shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {}
template<class U>
explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {}
shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); }
template<class U>
shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); }
~shared_ptr() { dec(); }
shared_ptr& operator=(const shared_ptr& s)
{
if(this!=&s)
{
dec();
pa = s.pa; pt=s.pt;
inc();
}
return *this;
}
T* operator->() const { return pt; }
T& operator*() const { return *pt; }
};
Where weak_ptr
interoperability is required a second counter (weak_count
) is required in aux
(will be incremented / decremented by weak_ptr
), and delete pa
must happen only when both the counters reach zero.
在weak_ptr
需要互操作性的情况下,需要第二个计数器 ( weak_count
) aux
(将增加/减少weak_ptr
),并且delete pa
必须仅在两个计数器都达到零时发生。
回答by Ziezi
How is the reference counting implemented?
引用计数是如何实现的?
A smart pointer implementation could be deconstructed, using policy-based class design1, into :
可以使用基于策略的类设计1将智能指针实现解构为:
Storage Policy
Ownership Policy
Conversion Policy
Checking Policy
存储策略
所有权政策
转换政策
检查政策
included as template parameters. Popular ownership strategies include: deep copy, reference counting, reference linking, and destructive copy.
包括作为模板参数。流行的所有权策略包括:深度复制、引用计数、引用链接和破坏性复制。
Reference counting tracks the number of smart pointers pointing to (owning2) the same object. When the number goes to zero, the pointee object is deleted3. The actual counter could be:
引用计数跟踪指向(拥有2)同一对象的智能指针的数量。当数字变为零时,指针对象被删除3。实际计数器可能是:
- Shared among smart pointer objects, where each smart pointer holds a pointer to the reference counter:
- 在智能指针对象之间共享,其中每个智能指针保存一个指向引用计数器的指针:
- Included only in an additional structure that adds an extra level of indirection the pointee object. Here the space overhead of holding a counter in each smart pointer is exchanged with slower access speed:
- 仅包含在一个额外的结构中,该结构为指针对象添加了额外的间接级别。这里每个智能指针中持有一个计数器的空间开销与较慢的访问速度交换:
Contained within the pointee object itself: intrusive reference counting. The disadvantage is that the object must be constructed a priori with facilities for counting:
Finally the method in your question, reference counting using doubly linked lists is called reference linking and it:
...[1] relies on the observation that you don't really need the actual count of smart pointer objects pointing to one pointee object; you only need to detect when that count goes down to zero. This leads to the idea of keeping an "ownership list" :
...[1] 依赖于您并不真正需要指向一个指针对象的智能指针对象的实际计数的观察;您只需要检测该计数何时降至零。这导致了保留“所有权清单”的想法:
The advantage of reference linking over reference counting is that the former does not use extra free store, which makes it more reliable: Creating a reference-linked smart pointer cannot fail. The disadvantage is that reference linking needs more memory for its bookkeeping (three pointers versus only one pointer plus one integer). Also, reference counting should be a bit speedier—when you copy smart pointers, only an indirection and an increment are needed. The list management is slightly more elaborate. In conclusion, you should use reference linking only when the free store is scarce. Otherwise, prefer reference counting.
引用链接相对于引用计数的优势在于前者不使用额外的空闲存储,这使其更加可靠:创建引用链接的智能指针不会失败。缺点是引用链接需要更多内存来进行记账(三个指针与仅一个指针加一个整数)。此外,引用计数应该更快一点——当你复制智能指针时,只需要一个间接和一个增量。列表管理稍微复杂一些。总之,您应该仅在免费商店稀缺时使用参考链接。否则,更喜欢引用计数。
Regarding your second question:
关于你的第二个问题:
Does it (
std::shared_ptr
) use a doubly linked list?
它 (
std::shared_ptr
)是否使用双向链表?
All that I could find in the C++ standard was:
我能在 C++ 标准中找到的所有内容是:
20.7.2.2.6 shared_ptr creation
...
7. [ Note: These functions will typically allocate more memory thansizeof(T)
to allow for internal bookkeeping structures such as the reference counts. —end note ]
20.7.2.2.6 shared_ptr 创建
...
7. [ 注意:这些函数通常会分配比sizeof(T)
允许内部簿记结构(例如引用计数)更多的内存。——尾注]
Which, in my opinion, excludes doubly linked lists, as they do not contain actual count.
在我看来,其中不包括双向链表,因为它们不包含实际计数。
Your third question:
你的第三个问题:
Are there any pitfalls for using the
std::shared_ptr
?
使用 有任何陷阱
std::shared_ptr
吗?
Reference management either counting or linking is a victim of the resource leak known as cyclic reference. Let's have an object A that holds a smart pointer to an object B. Also, object B holds a smart pointer to A. These two objects form a cyclic reference; even though you don't use any of them any more, they use each other. The reference management strategy cannot detect such cyclic references, and the two objects remain allocated forever.
引用管理无论是计数还是链接都是称为循环引用的资源泄漏的受害者。让我们有一个对象 A 持有一个指向对象 B 的智能指针。另外,对象 B 持有一个指向 A 的智能指针。这两个对象形成一个循环引用;即使您不再使用它们中的任何一个,它们也会相互使用。引用管理策略无法检测到这种循环引用,两个对象永远保持分配状态。
Because the implementation of shared_ptr
uses reference counting, cyclic references are potentially a problem. A cyclic shared_ptr
chain can be broken by changing the code so that one of the references is a weak_ptr
. This is done by assigning values between shared pointers and weak pointers, but a weak pointer doesn't affect the reference count. If the only pointers that point to an object are weak, the object is destroyed.
由于实现shared_ptr
使用引用计数,循环引用可能是一个问题。shared_ptr
可以通过更改代码来中断循环链,以便引用之一是weak_ptr
. 这是通过在共享指针和弱指针之间分配值来完成的,但弱指针不会影响引用计数。如果指向对象的唯一指针很弱,则该对象将被销毁。
1. Each design feature with multiple implementations if formulated as policy.
1. 如果制定为策略,则每个设计特征都有多个实现。
2. Smart pointers similarly to pointers that point to object allocated with new
, not only point to that object but also are responsible for its destruction and with the release of the memory it occupies.
2. 智能指针类似于指向分配的对象的指针,new
不仅指向该对象,还负责销毁它并释放它占用的内存。
3. With no further problems, if no other raw pointers are used and/or point to it.
3. 没有其他问题,如果没有使用其他原始指针和/或指向它。
[1] Modern C++ Design: Generic Programming and Design Patterns Applied. Andrei Alexandrescu, February 01, 2001
[1] 现代 C++ 设计:应用的通用编程和设计模式。安德烈·亚历山大雷斯库,2001 年 2 月 1 日
回答by sth
If you want to see all the gory details, you can have a look at the boost shared_ptr
implementation:
如果您想查看所有血腥细节,可以查看 boostshared_ptr
实现:
https://github.com/boostorg/smart_ptr
https://github.com/boostorg/smart_ptr
The reference counting seems to usually be implemented with a counter and platform specific atomic increment/decrement instructions or explicit locking with a mutex (see the atomic_count_*.hpp
files in the detail namespace).
引用计数似乎通常是通过计数器和平台特定的原子递增/递减指令或使用互斥锁的显式锁定来实现的(请参阅详细命名空间中的atomic_count_*.hpp
文件)。
回答by Jason
Are there any pitfalls for using the
std::tr1::shared_ptr
?
使用 有任何陷阱
std::tr1::shared_ptr
吗?
Yes, If you create cycles in your shared memory pointers, then the memory being managed by the smart pointer will not be recycled when the last pointer goes out of scope because there are still references to the pointer (i.e., the cycles cause the reference count to not go down to zero).
是的,如果您在共享内存指针中创建循环,那么当最后一个指针超出范围时,智能指针管理的内存将不会被回收,因为仍然存在对该指针的引用(即,循环导致引用计数不会降到零)。
For instance:
例如:
struct A
{
std::shared_ptr<A> ptr;
};
std::shared_ptr<A> shrd_ptr_1 = std::make_shared(A());
std::shared_ptr<B> shrd_ptr_2 = std::make_shared(A());
shrd_ptr_1->ptr = shrd_ptr_2;
shrd_ptr_2->ptr = shrd_ptr_1;
Now, even if shrd_ptr_1
and shrd_ptr_2
go out of scope, the memory they are managing is not reclaimed because the ptr
member of each are pointing to each other. While this is a very naive example of such a memory cycle, it can, if you use these types of pointers without any discipline, occur in a much more nefarious and hard-to-track fashion. For instance, I could see where trying to implement a circular linked-list where each next
pointer is a std::shared_ptr
, if you're not too careful, could result in problems.
现在,即使shrd_ptr_1
和shrd_ptr_2
走出去的范围,他们所管理的内存不回收,因为ptr
每个成员都指向对方。虽然这是这种内存循环的一个非常幼稚的例子,但如果您在没有任何纪律的情况下使用这些类型的指针,它可能会以更加邪恶和难以跟踪的方式发生。例如,我可以看到尝试实现每个next
指针都是 a的循环链接列表的位置std::shared_ptr
,如果您不太小心,可能会导致问题。