32

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?

Srikanth
  • 11,780
  • 23
  • 72
  • 92

7 Answers7

71

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.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 2
    The linked-list approach avoids the extra allocation, but is very difficult to make "thread-safe" without a global mutex. ("thread-safe" as in "as thread-safe as a raw pointer") – curiousguy Oct 10 '11 at 14:27
  • 2
    Also if you use `make_shared`, it may also avoid the extra allocation by putting the allocated object and instance counter in a single block of memory. – Ferruccio Aug 26 '12 at 12:33
  • Didn't know about the linked list approach. I can see a few advantages to it over the first approach which I'm familiar with. Namely the lack of the extra "small block of memory" and the lack of need to worry about arithmetic overflow. – Assimilater Jul 14 '16 at 03:45
  • @Assimilater - I don't think there's any real advantage to the linked list method. You do have to maintain the list, which has implications for thread safety and performance. Whereas the extra block allocation usually disappears if you use `make_shared` and everyone *should* be using `make_shared` anyway. Also if arithmetic overflow is a real possibility in this context, I suspect there are far more serious problems with the code base. – Ferruccio Jul 14 '16 at 12:29
  • @Ferruccio that last point, I hear ya. Probably shouldn't have more than 2^16 copies of a pointer. How does `make_shared` remove the extra allocation for the reference counter? I'll have to try implementing both systems I guess to see how thread safety and performance are affected (I'm having a hard time seeing it off-hand) – Assimilater Jul 14 '16 at 15:10
  • 1
    @Assimilater - you allocate a block big enough to hold both the object and reference count. You then store the reference count just past the last byte of the object. – Ferruccio Jul 14 '16 at 16:23
  • @Ferruccio Oh, so the point is rather than two allocations it's one allocation, and thus less fragmented? – Assimilater Jul 14 '16 at 21:59
  • @Assimilater less fragmented _and_ less CPU time calling the allocator (up to 50% less, I suppose) – underscore_d Sep 04 '16 at 23:12
  • @Ferruccio "everyone should be using make_shared anyway. " That's not true at all. There are numerous reasons to not use it tied to both performance and functionality. – xaxxon May 22 '17 at 15:03
4

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.

Gabriel Southern
  • 9,602
  • 12
  • 56
  • 95
  • -1. All smart pointers that refer to the same object must *share* a single reference count. The object holding the reference count is allocated by the first smart pointer object, and *pointers to it* (NOT the object itself) are copied when the smart pointer is copied. – j_random_hacker Apr 07 '09 at 11:45
  • Agree at j_random_hacker. The count is unique for each raw pointer and shared by all shared_ptr that refer to the same raw pointer. Usually it is allocated as a separate chunk of memory so smart_ptr hold two internal ptrs, one for the reference count and another for the pointer itself. – David Rodríguez - dribeas Apr 07 '09 at 13:27
  • -1 for static variable. Unless you are implementing a reference-counted smart pointer to a singleton object, you can't use statics to implement the reference counting. – John Dibling Apr 07 '09 at 14:17
  • I guess I messed up my wording, I MEANT that each pointer has a reference count and that each smart pointer references it. Fixed it (I hope) –  Apr 08 '09 at 08:43
  • I think perhaps Ferruccio's answer would make a better accepted answer –  Apr 08 '09 at 08:45
  • @Dan: I changed the answer and accepted Ferruccio's. The link did help me understand about smart pointers better. Thanks! – Srikanth Apr 08 '09 at 13:52
3

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.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
3

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

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.

bayda
  • 13,365
  • 8
  • 39
  • 48
2

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.

Cătălin Pitiș
  • 14,123
  • 2
  • 39
  • 62
0

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.

Abhishek A Udupa
  • 401
  • 6
  • 13