How do shared pointers work?

How do shared pointers know how many pointers point to this object? (shared_ptr, in this case)

+33
c ++ c ++ 11 shared-ptr
May 10 '10 at 1:32 p.m.
source share
5 answers

In principle, shared_ptr has two pointers: a pointer to a shared object and a pointer to a structure containing two reference counts: one for โ€œstrong linksโ€ or ownership links, and one for โ€œweak linksโ€, or links that don't have the right property.

When copying shared_ptr the copy constructor increases the number of links. When you destroy shared_ptr , the destructor decreases the reference count and checks to see if the reference count is zero; if so, the destructor deletes the shared object, because no shared_ptr points to it more.

Weak reference counting is used to support weak_ptr ; basically, at any time when a weak_ptr is created from shared_ptr , the count of the weak link is increased, and at any point in time, the weak reference counter is destroyed. As long as either a strong reference counter or a weak reference counter is greater than zero, the structure of the reference account will not be destroyed.

Effectively, until a strong reference count is greater than zero, the shared object will not be deleted. Until a strong reference count or a weak reference count is non-zero, the link count structure will not be deleted.

+52
May 10 '10 at 1:36 p.m.
source share

I generally agree with James McNellis's answer. However, there is one more point that should be mentioned.

As you know, shared_ptr<T> can also be used when the type T not fully defined.

I.e:

 class AbraCadabra; boost::shared_ptr<AbraCadabra> myPtr; // ... 

This will compile and work. Unlike many other smart pointer implementations that actually require encapsulated types to be fully defined for their use. This is because the smart pointer must know in order to delete an encapsulated object when it is no longer referenced, and to delete an object one must know what it is.

This is achieved using the following trick: shared_ptr actually consists of the following:

  • Opaque object pointer
  • General link counts (James McNellis description)
  • A pointer to a dedicated factory that knows how to destroy your object.

The above factory is a helper object with a single virtual function that should properly remove your object.

This factory is actually created when you assign a value to your generic pointer.

That is, the following code

 AbraCadabra* pObj = /* get it from somewhere */; myPtr.reset(pObj); 

This factory is located here. Note. The reset function is actually a template . It actually creates a factory for the specified type (the type of the object passed as a parameter). Here your type should be fully defined. That is, if it is not yet defined, you will get a compilation error.

Please note: if you actually create an object of a derived type (derived from AbraCadabra ) and assign it to shared_ptr - it will be deleted correctly, even if your destructor is not virtual. shared_ptr always deletes an object according to the type that it sees in the reset function.

So, shared_ptr is a pretty complicated version of a smart pointer. It gives amazing flexibility. . However, you should be aware that this flexibility is achieved at the price of extremely poor performance compared to other possible smart pointer implementations.

On the other hand, there are so-called "intrusive" smart pointers. They do not have such flexibility, however, on the contrary, they give better performance.

The benefits of shared_ptr compared to intrusive smart pointers:

  • Very flexible use. Only the encapsulated type needs to be defined only when shared_ptr assigned. This is very important for large projects, significantly reduces dependency.
  • There should not be a virtual destructor in the encapsulated type, but polymorphic types will be deleted correctly.
  • Can be used with weak pointers.

Cons of shared_ptr compared to intrusive smart pointers:

  • Very barbaric performance and heap memory waste. When assigned, allocates 2 more objects: reference counters, plus factory (memory waste, slow). However, this only happens on reset . When one shared_ptr assigned to another, nothing else is allocated.
  • The above may throw an exception. (condition for lack of memory). In contrast, intuitive smart pointers can never be thrown (except for process exceptions related to invalid memory access, stack overflows, etc.).
  • Removing your object is also slow: you need to free two more structures.
  • When working with intrusive smart pointers, you can freely mix smart pointers with the source. This is normal because the actual reference count is inside the object itself, which is single. Unlike shared_ptr you can not mix with raw pointers.

    AbraCadabra * pObj = / * get it from somewhere * /; myPtr.reset (PObj); // ... pObj = myPtr.get (); boost :: shared_ptr myPtr2 (pObj); // oops

The above will fail.

+14
May 10 '10 at 15:30
source share

There are at least three known mechanisms.

External counters

When the first general pointer to an object is created, a separate reference counting object is created and initialized to 1. When a copy is specified, the reference count is incremented; when the pointer is destroyed, it decreases. Assigning a pointer increases one counter and decreases another (in that order, otherwise self-separation ptr=ptr will be interrupted). If the reference count goes to zero, no more pointers exist and the object is deleted.

Internal counters

The internal counter requires that the specified object have a counter field. This is usually achieved by obtaining a specific base class. In return, this saves the allocation of the reference counter heap and allows you to repeat the creation of common pointers from the source pointers (with external counters, you will get two counts for one object)

Circular links

Instead of using a counter, you can save all common pointers to an object in a pie graph. The first pointer created points to itself. When you copy the pointer, you paste the copy into the circle. When you delete it, you remove it from the circle. But when the destroyed pointer points to itself, i.e. When it is a single pointer, you delete the object with the pointer.

The downside is that removing a node from a circular, simply connected list is pretty expensive, since you need to iterate over all the nodes to find the predecessor. This can be especially painful due to the poor locality of the links.

Options

The second and third idea can be combined: the base class can be part of this circular graph, and not contain an account. Of course, this means that an object can only be deleted when it points to itself (cycle length 1, other pointers to it). Again, the advantage is that you can create smart pointers from weak pointers, but the poor performance of removing a pointer from the chain remains a problem.

The exact graph structure for idea 3 is not a big deal. You can also create a binary tree structure with the specified object in the root. Again, the tough operation is to remove the generic node pointer from this graph. The advantage is that if you have many pointers to many threads, the growing part of the graph is not a very decisive operation.

+10
May 10 '10 at 14:34
source share

They contain an internal reference counter, which is incremented in the constructor / destination instance of the shared_ptr instance and decremented in the destructor. When the counter reaches zero, the held pointer will be deleted.

Here is the Boost library of documentation for smart pointers. I think the implementation of TR1 is basically the same as boost::shared_ptr .

0
May 10 '10 at 1:35 pm
source share

"A generic pointer is a smart pointer (a C ++ object with an overloaded operator * () and operator โ†’ ()) that stores a pointer to an object and a pointer to a shared reference count. Each time a copy of a smart pointer is created using the copy constructor, the reference count is incremented. When the shared pointer is destroyed, the reference count for its object is decremented. Common pointers built from the source pointers initially have a reference count of 1. When the reference count reaches 0, the pointed object is destroyed, and the memory it Nima exempt You do not need to explicitly destroy objects:. This will be done automatically when running the last destructor pointer ". From here .

0
May 10, '10 at 13:35
source share



All Articles