我一直在考虑使用共享指针,我知道如何自己实现 - 不想这样做,所以我正在尝试std::tr1::shared_ptr
,我有几个问题..
如何实施引用计数?它是否使用双向链表? (顺便说一下,我已经用Google搜索了,但我找不到任何可靠的信息。)
使用std::tr1::shared_ptr
是否有任何陷阱?
答案 0 :(得分:50)
shared_ptr
必须管理一个引用计数器和一个删除函数的携带,该函数由初始化时给出的对象类型推导出来。
shared_ptr
类通常包含两个成员:T*
(由operator->
返回并在operator*
中取消引用)和aux*
其中aux
1}}是一个内部抽象类,包含:
virtual destroy()=0;
这样的aux
类(实际名称取决于实现)是由一系列模板化类派生的(根据显式构造函数给出的类型进行参数化,比如U
派生自{{1} }),添加:
T
相同,但与实际类型相同:这需要正确管理T*
作为具有多个T
的基础的所有情况派生层次结构中的U
)T
对象的副本(或默认deletor
只执行删除deletor
,其中p
是{{1}上面)简化的草图可以是这个:
p
在需要U*
互操作性的情况下,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; }
};
中需要第二个计数器(weak_ptr
)(将weak_count
增加/减少),aux
必须在两个计数器都达到零时发生。
答案 1 :(得分:23)
如何实施引用计数?
可以使用policy-based class design 1 将智能指针实现解构为:
存储政策
所有权政策
转化政策
检查政策
包含在模板参数中。 流行的所有权策略包括:深层复制,引用计数,引用链接和破坏性复制。
引用计数跟踪指向(拥有 2 )同一对象的智能指针的数量。当数字变为零时,指针对象被删除 3 。实际的计数器可能是:
包含在指针对象本身内:侵入式引用计数。缺点是对象必须先验地构建,并具有计数功能:
最后,您的问题中的方法,使用双向链接列表的引用计数称为引用链接,它是:
... [1]依赖于观察,你并不真正需要指向一个指针对象的智能指针对象的实际数量;你只需要检测该计数何时下降到零。这导致了保持所有权列表的想法。 :
引用链接优于引用计数的优点是前者不使用额外的自由存储,这使得它更可靠:创建引用链接的智能指针不会失败。缺点是 该引用链接需要更多的内存用于其簿记(三个指针与仅一个指针加一个整数)。此外,引用计数应该更快一些 - 当您复制智能指针时,只需要间接和增量。列表管理稍微复杂一些。结论, 只有在免费商店稀缺时才应使用引用链接。否则,更喜欢引用计数。
是否(
std::shared_ptr
)使用双向链表?
我在C ++标准中找到的所有内容都是:
20.7.2.2.6 shared_ptr创建
...
7. [注意:这些函数通常会分配比sizeof(T)
更多的内存,以允许内部簿记结构,例如引用计数。 - 后注]
在我看来,这不包括双重链表,因为它们不包含实际计数。
使用
std::shared_ptr
是否有任何陷阱?
参考管理计数或链接是资源泄漏的受害者,称为循环引用。让对象A拥有一个指向对象B的智能指针。另外,对象B保存一个指向A的智能指针。这两个对象形成一个循环引用;即使你不再使用它们中的任何一个,它们也互相使用。参考管理策略无法检测此类循环引用,并且这两个对象将永远分配。
因为shared_ptr
的实现使用引用计数,所以循环引用可能是个问题。可以通过更改代码来中断循环shared_ptr
链,以使其中一个引用为weak_ptr
。这是通过在共享指针和弱指针之间分配值来完成的,但弱指针不会影响引用计数。如果指向对象的唯一指针很弱,则该对象将被销毁。
1。每个设计功能都有多个实现,如果制定为策略。
2。智能指针类似于指向使用new
分配的对象的指针,不仅指向该对象,还负责其销毁以及释放它占用的内存。
3。没有其他问题,如果没有使用其他原始指针和/或指向它。
[1]现代C ++设计:应用通用编程和设计模式。 Andrei Alexandrescu,2001年2月1日
答案 2 :(得分:3)
如果您想查看所有血腥细节,可以查看boost shared_ptr
实现:
https://github.com/boostorg/smart_ptr
引用计数似乎通常使用计数器和平台特定的原子递增/递减指令或使用互斥锁显式锁定(请参阅detail namespace中的atomic_count_*.hpp
文件)。
答案 3 :(得分:3)
使用
std::tr1::shared_ptr
是否有任何陷阱?
是的,如果在共享内存指针中创建循环,那么当最后一个指针超出范围时,由智能指针管理的内存将不会被回收,因为仍有对指针的引用(即循环原因)引用计数不会降到零。)
例如:
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;
现在,即使shrd_ptr_1
和shrd_ptr_2
超出范围,它们管理的内存也不会被回收,因为每个ptr
成员都指向对方。虽然这是一个非常天真的这种记忆周期的例子,但如果你使用这些类型的指针而没有任何规则,它可以以更加邪恶和难以跟踪的方式出现。例如,我可以看到在哪里尝试实现一个循环链表,其中每个next
指针都是std::shared_ptr
,如果你不太小心,可能会导致问题。