引用计数智能指针的引用计数是如何工作的?

发布于 2024-07-16 17:48:37 字数 204 浏览 12 评论 0原文

换句话说,实现如何跟踪计数?

是否维护了一个类似地图的对象,所有 shared_ptr 实例都可以访问该对象,其键是指针的地址,值是引用的数量? 如果我必须实现一个 shared_ptr,这是我想到的第一个想法。

使用这些引用计数智能指针是否有可能发生内存泄漏? 如果是这样,我该如何避免它们?

In other words, how does the implementation keeps track of the count?

Is there a map-like object maintained which is accessible by all the shared_ptr instances whose key is the pointer's address and value is the number of references? If I've to implement a shared_ptr, this is the first idea that's coming to my mind.

Is there a possibility for a memory leak in case of these reference-counting smart pointers? If so, how can I avoid them?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

简单气质女生网名 2024-07-23 17:48:37

我见过两种不同的非侵入式方法:

  1. 智能指针分配一个小的
    内存块来包含
    参考计数器。 每份副本
    然后智能指针接收一个
    指向实际对象的指针和
    指向引用计数的指针。
  2. 除了对象指针之外,
    每个智能指针包含一个
    上一个和下一个指针,从而
    形成一个双向链表
    指向特定的智能指针
    目的。 引用计数是
    隐含在列表中。 当一个聪明人
    指针被复制,它将自身添加到
    列表。 销毁后,每个
    智能指针将自身从
    列表。 如果这是最后一张
    然后它释放列表
    以及引用的对象。

如果你去这里并滚动到底部,有一个很好的图表解释了这些方法更加清晰。

I've seen two different non-intrusive approaches to this:

  1. The smart pointer allocates a small
    block of memory to contain the
    reference counter. Each copy of the
    smart pointer then receives a
    pointer to the actual object and a
    pointer to the reference count.
  2. In addition to an object pointer,
    each smart pointer contains a
    previous and next pointer, thereby
    forming a doubly-linked list of
    smart pointers to a particular
    object. The reference count is
    implicit in the list. When a smart
    pointer is copied, it adds itself to
    the list. Upon destruction, each
    smart pointer removes itself from
    the list. If it's the last one in
    the list it then frees the
    referenced object as well.

If you go here and scroll to the bottom, there is an excellent diagram which explains these methods much more clearly.

装迷糊 2024-07-23 17:48:37

许多答案都解决了引用计数的存储方式(它存储在保存相同本机指针的所有共享指针的共享内存中),但大多数答案都回避了泄漏问题。

使用引用计数指针泄漏内存的最简单方法是创建循环。 举个例子,一个双向链表,其中所有指针都是至少有两个元素的shared_ptr,保证不会被删除。 即使外部指针被释放,内部指针仍然会计数,并且引用计数不会达到 0。至少在最简单的实现中是这样。

循环问题最简单的解决方案是将shared_ptr(引用计数指针)与不共享对象所有权的弱指针混合。

共享指针将共享资源(指针)和附加的reference_count信息。 当您使用弱指针时,引用计数会加倍:存在共享指针引用计数和弱指针引用计数。 每当共享指针计数达到0时,资源就会被释放,但reference_count信息将保持活动状态,直到最后一个弱指针被释放。

在双向链表中,外部引用保存在shared_ptr中,而内部链接只是weak_ptr。 每当没有外部引用(shared_ptr)时,列表的元素就会被释放,删除弱引用。 最后所有弱引用都被删除,指向每个资源的最后一个弱指针释放了reference_count信息。

它不像上面的文字那样令人困惑......我稍后再试一次。

Many answers address the way the reference count is stored (it is stored in a shared memory for all shared_ptr that hold the same native pointer), but most elude the problem of leaks.

The easiest way of leaking memory with reference counted pointers is creating cycles. As an example, a doubly linked list where all the pointers are shared_ptr with at least two elements is guaranteed not to be deleted. Even if external pointers are freed, the internal pointers will still count, and the reference count will not reach 0. That is, at least, with the most naïve implementation.

The easiest solution to the cycle problem is mixing shared_ptr (reference counted pointers) with weak pointers that do not share the ownership of the object.

Shared pointers will share both the resource (pointer) and the additional reference_count information. When you use weak pointers, the reference count is doubled: there is a shared pointer reference count and a weak pointer reference count. The resource is released whenever the shared pointer count reaches 0, but the reference_count information is left alive until the last weak pointer is released.

In the doubly linked list, the external reference is held in a shared_ptr, while the internal links are just weak_ptr. Whenever there are no external references (shared_ptr) the elements of the list are released, deleting the weak references. At the end all weak references have been deleted and the last weak pointer to each resources frees the reference_count information.

It is less confusing than the above text seems... I'll try again later.

长梦不多时 2024-07-23 17:48:37

每个智能指针对象都包含一个共享引用计数 - 每个原始指针都有一个共享引用计数。

您可以查看这篇文章。 此实现将它们存储在一个单独的对象中,并进行复制。 您还可以查看 boost 文档 或查看关于智能指针的维基百科文章

Each smart pointer object contains a shared reference count - one for every raw pointer.

You could take a look at this article. This implementation stores these in a separate object which is copied around. You could also take a look at boost's documentation or take a look at the Wikipedia article on smart pointers.

他夏了夏天 2024-07-23 17:48:37

使用引用计数智能指针造成内存泄漏非常容易。 只需创建图形中具有循环的任何类似图形的对象结构即可。 循环中的对象会互相阻止被释放。 这无法自动解决 - 例如,当您创建双链接列表时,您必须注意不要一次删除多个对象。

Creating a memory leak with reference-counting smart pointers is very easy. Just create any graph-like structure of objects that has a cycle in the graph. The objects in the cycle will prevent each other from being released. This can't be resolved automatically - for example, when you create a double-link list you have to take care of never removing more than one object at a time.

尐偏执 2024-07-23 17:48:37

不会。shared_ptr 只是保留一个额外的指针用于引用计数。

当您复制shared_ptr对象时,它会复制带有引用计数的指针,增加它,然后复制所包含对象上的指针。

No. shared_ptr just keep one additional pointer for reference counting.

When you make copy of shared_ptr object it copy pointer with count of references, increase it, and copy pointer on contained object.

苯莒 2024-07-23 17:48:37

据我记得,《Effective C++》的一章里讨论过引用计数指针的问题。

原则上,您有“轻”指针类,其中包含一个指向持有引用的类的指针,该引用知道递增/递减引用并销毁指针对象。 该引用计数类指向要引用的对象。

As far as I remember, there was the problem of reference counting pointer treated in a chapter of Effective C++.

In principle, you have the "light" pointer class, containing a pointer to a class holding the reference which knows to increment/decrement reference and destroy the pointer object. That reference counting class points to the object to be referenced.

未蓝澄海的烟 2024-07-23 17:48:37

实现 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 对象之间共享,这就是为什么我们需要一个引用计数系统来跟踪有多少个 SharedPtr 对象正在管理该指针指向的内存在程序生命周期内的任何时间点。

引用计数器 r_count

这是一个指向整数类型变量的指针,它也在管理相同内存的多个 SharedPtr 对象之间共享。 这是共享的,因为管理内存的每个 SharedPtr 对象都应该知道管理同一内存的每个其他 SharedPtr 对象的计数。 实现此目的的方法是使用由同一系列的 SharedPtr 对象引用的公共引用计数器。

每次具体化一个新的 SharedPtr 对象来管理已由其他 SharedPtr 对象管理的内存时,r_count 就会增加 1。当一个 SharedPtr 对象死亡,因此其他 SharedPtr 对象“知道”管理该家族维护的内存的家族成员之一已经去世,并且不再管理该家族记忆。

默认构造函数

当一个新的 SharedPtr 对象被创建并由堆分配的内存初始化时,会调用此构造函数,其中内部指针 m_ptr 被初始化为需要管理的堆分配内存地址。 由于这是对该指针的第一个也是唯一的引用,因此引用计数器 r_count 被设置为 1。这里没有发生任何有趣的事情。

复制构造函数和复制赋值

这是“真正的”引用计数发生的地方。

每当使用另一个 SharedPtr 对象创建新的 SharedPtr 对象或使用现有的 SharedPtr 引用另一个 SharedPtr 时,即基本上,当创建一个新的 SharedPtr 对象(现有的或新创建的)来管理已由其他 SharedPtr 对象管理的内存时,内部指针变量这个新管理器的m_ptr指向要管理的内存地址,并且该族的引用计数增加1。

析构函数

智能指针旨在释放他们死后所管理的记忆。 对于SharedPtr,它确保在释放内存之前没有其他对正在管理的内存的引用。 所有这些都发生在对象的析构函数中。

正如您在代码中看到的,只有当内存的引用计数为 0 时,对象才会在其死亡之前释放内存。

这很重要,因为您会发现,如果 SharedPtr 对象在 r_count 不为 0 时释放内存,则管理同一内存的其他 SharedPtr 对象会在之后的某个时间尝试访问它结果将是程序崩溃。

SharedPtr 通过将释放内存的责任交给管理内存的最后一个存活对象来确保这种情况不会发生。 由于 SharedPtr 的设计,所有这一切都会自动发生,无需程序员的干预。

这就是引用计数的工作原理。

引用计数就像几个室友的例行公事:最后离开房间的人有责任锁上大门。 为了让这一过程顺利进行,每个室友都应该知道他是否是最后一个离开房间的人。

The class that implements RC basically keeps count of number of references (from other objects of the class, including its own) to the memory address that it is managing. The memory is freed only when the reference count to the memory address is zero.

Let’s look at some code:

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;
            }
        }
    }
};

Here’s the detail of how the SharedPtr class above works:

Internal Variables

The internal pointer m_ptr

A pointer of the SharedPtr class, which is the actual pointer used to manage the memory in question. This pointer variable is shared across multiple SharedPtr objects, which is why we need a reference counting system to keep track of how many SharedPtr objects are managing the memory pointed to by this pointer at any point of time during a program’s lifetime.

The reference counter r_count

This is a pointer to an integer type variable, which is also shared across multiple SharedPtr objects managing the same memory. This is shared because, every SharedPtr object managing the memory should be aware of the count of every other SharedPtr object that is managing the same memory. The way to achieve this is by having a common reference counter referred to by SharedPtr objects of the same family.

Every time a new SharedPtr object is materialized to manage a memory already being managed by other SharedPtr object/s, the r_count goes up by 1. It is also decremented by 1 when a SharedPtr object dies, so that other SharedPtr objects ‘know’ that one of their family members who was managing the memory maintained by the family has died and no-longer managing the memory.

Default Constructor

When a new SharedPtr object is created and initialized by a heap allocated memory, this constructor is called where the internal pointer m_ptr is initialized to the heap allocated memory address that needs managing. Since this is the first and the only reference to that pointer, the reference counter r_count is set to 1. Nothing interesting happens here.

Copy Constructor and Copy Assignment

This is where the ‘real’ reference counting happens.

Whenever a new SharedPtr object is made using another SharedPtr object or an existing SharedPtr is made to reference another SharedPtr i.e basically when a new SharedPtr object (either existing or newly created) is made to manage a memory that was already being managed by other SharedPtr object/s, the the internal pointer variable m_ptr of this new manager is made to point at the memory address to be managed and the reference count of the family goes up by 1.

Destructor

Smart Pointers are designed to free the memory they’re managing when they die. In the case of SharedPtr, it ensures that there are no other references to the memory being managed before freeing the memory. All of these happen in the object’s Destructor.

As you can see in the code, the object frees the memory only if the reference count to the memory is 0, before it dies.

This is important because, you see, if a SharedPtr object frees the memory when r_count isn’t 0, other SharedPtr objects managing the same memory would try to access it sometime after and the result would be a program crash.

The SharedPtr ensures this does not happen by giving the responsibility of freeing memory to the last surviving object that is managing a memory. Due to the design of the SharedPtr, all of this happens automatically without the programmer’s intervention.

This is how reference counting works.

Reference counting is like the routine of couple of roommates: who leaves the room last has the responsibility of locking the main door. For that to happen seamlessly, every roommate should be aware if he’s the last one to leave the room.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文