换句话说,实现如何跟踪计数?
是否存在一个类似于地图的对象,所有shared_ptr
个实例都可以访问它们,其键是指针的地址,值是引用的数量?如果我要实现shared_ptr
,这是我想到的第一个想法。
在这些引用计数智能指针的情况下是否存在内存泄漏的可能性?如果是这样,我该如何避免它们?
答案 0 :(得分:64)
我已经看到了两种不同的非侵入式方法:
如果你转到here并滚动到底部,那么可以更清楚地解释这些方法。
答案 1 :(得分:4)
使用引用计数智能指针创建内存泄漏非常简单。只需创建任何在图中具有循环的对象的类似图形的结构。循环中的对象将阻止彼此释放。这无法自动解决 - 例如,当您创建双链接列表时,您必须注意永远不要一次删除多个对象。
答案 2 :(得分:3)
每个智能指针对象都包含一个共享引用计数 - 每个原始指针都有一个。
您可以查看this文章。此实现将这些存储在一个单独的对象中,该对象被复制。您还可以查看boost's documentation或查看智能指针上的Wikipedia article。
答案 3 :(得分:2)
没有。 shared_ptr只保留一个额外的指针用于引用计数。
当您复制shared_ptr对象时,它会复制带有引用计数的指针,增加它,并在包含的对象上复制指针。
答案 4 :(得分:2)
据我记忆,在Effective C ++的一章中有引用计数指针的问题。
原则上,你有一个“light”指针类,它包含一个指向保存引用的类的指针,该类知道增加/减少引用并销毁指针对象。该引用计数类指向要引用的对象。
答案 5 :(得分:2)
许多答案解决了存储引用计数的方式(它存储在共享内存中,用于保存相同本机指针的所有shared_ptr),但大多数都避免了泄漏问题。
使用引用计数指针泄漏内存的最简单方法是创建周期。例如,保证不删除所有指针都是至少有两个元素的shared_ptr的双向链表。即使释放了外部指针,内部指针仍会计数,引用计数也不会达到0.这至少是最天真的实现。
解决循环问题的最简单方法是将shared_ptr(引用计数指针)与不共享对象所有权的弱指针混合。
共享指针将共享资源(指针)和附加的reference_count信息。当您使用弱指针时,引用计数加倍:共享指针引用计数和弱指针引用计数。只要共享指针计数达到0,就会释放资源,但reference_count信息保持活动状态,直到最后一个弱指针被释放。
在双向链表中,外部引用保存在shared_ptr中,而内部链接只是weak_ptr。只要没有外部引用(shared_ptr),就会释放列表的元素,删除弱引用。最后,所有弱引用都被删除,并且指向每个资源的最后一个弱指针释放了reference_count信息。
它比上面的文字似乎没有那么令人困惑......我稍后会再试一次。
答案 6 :(得分:0)
实现RC的类基本上保留对所管理的内存地址的引用次数(从该类的其他对象,包括其自身)。仅当对内存地址的引用计数为零时,才释放内存。
让我们看一些代码:
template <class T>
class SharedPtr
{
T* m_ptr;
unsigned int* r_count;
public:
//Default Constructor
SharedPtr(T* ptr) :m_ptr{ ptr }, r_count{ ptr ? new unsigned int : nullptr }
{
if (r_count)
{
*r_count = 1;
}
}
//Copy Constructor
SharedPtr(SharedPtr& ptr) :m_ptr{ ptr.m_ptr }, r_count{ ptr.m_ptr ? new unsigned int : nullptr }
{
if (ptr.r_count)
{
++(*ptr.r_count);
r_count = ptr.r_count;
m_ptr = ptr.m_ptr;
}
}
//Copy Assignment
SharedPtr& operator=(SharedPtr& ptr)
{
if (&ptr == this)
return *this;
if (ptr.r_count)
{
delete m_ptr;
++(*ptr.r_count);
r_count = ptr.r_count;
m_ptr = ptr.m_ptr;
}
return *this;
}
//Destructor
~SharedPtr()
{
if (r_count)
{
--(*r_count);
if (!(*r_count))
{
delete m_ptr;
delete r_count;
}
}
}
};
以下是上述SharedPtr
类的工作原理的详细信息:
内部变量
内部指针 m_ptr
SharedPtr
类的指针,它是用于管理相关内存的实际指针。该指针变量在多个SharedPtr
对象之间共享,这就是为什么我们需要一个引用计数系统来跟踪有多少SharedPtr
个对象在此期间的任何时间管理该指针指向的内存程序的生命周期。
参考计数器 r_count
这是一个指向整数类型变量的指针,该变量也在管理同一内存的多个SharedPtr
对象之间共享。这是共享的,因为管理内存的每个SharedPtr
对象都应知道管理同一内存的每个其他SharedPtr
对象的计数。实现此目的的方法是通过使用同一族的SharedPtr
个对象引用的公共引用计数器。
每次实现一个新的SharedPtr
对象来管理已经由其他SharedPtr
对象管理的内存时,r_count就会增加1。当{{ 1}}对象死亡,因此其他SharedPtr
对象“知道”正在管理该家庭维护的记忆的一个家庭成员已经死亡,不再管理该记忆。
默认构造函数
当新的SharedPtr
对象由堆分配的内存创建并初始化时,将调用此构造函数,其中内部指针SharedPtr
初始化为需要管理的堆分配的内存地址。由于这是对该指针的第一个也是唯一的引用,因此引用计数器m_ptr
设置为1。这里没有任何有趣的事情。
复制构造函数和复制分配
这是“真实”参考计数发生的地方。
每当使用另一个r_count
对象创建一个新的SharedPtr
对象或使现有的SharedPtr
引用另一个SharedPtr
时,即基本上是一个新的SharedPtr
使对象(现有的或新创建的)管理已由其他SharedPtr
对象管理的内存,使该新管理器的内部指针变量SharedPtr
指向要管理的内存地址和系列的引用计数增加1。
析构函数
智能指针的设计目的是在其死亡时释放正在管理的内存。在m_ptr
的情况下,它确保释放内存之前没有其他对正在管理的内存的引用。所有这些都发生在对象的Destructor中。
如您在代码中所见,对象在死亡之前仅在对内存的引用计数为0时才释放内存。
这很重要,因为您会看到,如果r_count不为0时,SharedPtr
对象释放了内存,则其他管理同一内存的SharedPtr
对象将在此后的某个时间尝试访问它,结果将会导致程序崩溃。
SharedPtr
通过将负责释放内存的责任分配给最后一个正在管理内存的对象来确保不会发生这种情况。由于SharedPtr
的设计,所有这些都会自动发生,而无需程序员干预。
这是引用计数的工作方式。
引用计数就像一对室友的例行程序:最后离开房间的人有责任锁上大门。为了使这一过程无缝发生,每个室友都应该知道他是否是最后一个离开房间的人。