I've been thinking about using shared pointers, and I know how to implement one myself--Don't want to do it, so I'm trying std::tr1::shared_ptr
,and I have couple of questions...
How is the reference counting implemented? Does it use a doubly linked list? (Btw, I've already googled, but I can't find anything reliable.)
Are there any pitfalls for using the std::tr1::shared_ptr
?
It is a reference counting ownership model i.e. it maintains the reference count of its contained pointer in cooperation with all copies of the std::shared_ptr. So, the counter is incremented each time a new pointer points to the resource and decremented when destructor of the object is called.
To implement weak_ptr , the "counter" object stores two different counters: The "use count" is the number of shared_ptr instances pointing to the object. The "weak count" is the number of weak_ptr instances pointing to the object, plus one if the "use count" is still > 0.
Smart pointer in C++, can be implemented as template class, which is overloaded with * and -> operator. auto_ptr, shared_ptr, unique_ptr and weak_ptr are the forms of smart pointer can be implemented by C++ libraries.
The shared_ptr type is a smart pointer in the C++ standard library that is designed for scenarios in which more than one owner might have to manage the lifetime of the object in memory.
shared_ptr
must manage a reference counter and the carrying of a deleter functor that is deduced by the type of the object given at initialization.
The shared_ptr
class typically hosts two members: a T*
(that is returned by operator->
and dereferenced in operator*
) and a aux*
where aux
is a inner abstract class that contains:
virtual destroy()=0;
Such aux
class (the actual name depends on the implementation) is derived by a family of templatized classes (parametrized on the type given by the explicit constructor, say U
derived from T
), that add:
T*
, but with the actual type: this is needed to properly manage all the cases of T
being a base for whatever U
having multiple T
in the derivation hierarchy)deletor
object given as deletion policy to the explicit constructor (or the default deletor
just doing delete p
, where p
is the U*
above)A simplified sketch can be this one:
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; } };
Where weak_ptr
interoperability is required a second counter (weak_count
) is required in aux
(will be incremented / decremented by weak_ptr
), and delete pa
must happen only when both the counters reach zero.
How is the reference counting implemented?
A smart pointer implementation could be deconstructed, using policy-based class design1, into :
Storage Policy
Ownership Policy
Conversion Policy
Checking Policy
included as template parameters. Popular ownership strategies include: deep copy, reference counting, reference linking, and destructive copy.
Reference counting tracks the number of smart pointers pointing to (owning2) the same object. When the number goes to zero, the pointee object is deleted3. The actual counter could be:
Contained within the pointee object itself: intrusive reference counting. The disadvantage is that the object must be constructed a priori with facilities for counting:
Finally the method in your question, reference counting using doubly linked lists is called reference linking and it:
...[1] relies on the observation that you don't really need the actual count of smart pointer objects pointing to one pointee object; you only need to detect when that count goes down to zero. This leads to the idea of keeping an "ownership list" :
The advantage of reference linking over reference counting is that the former does not use extra free store, which makes it more reliable: Creating a reference-linked smart pointer cannot fail. The disadvantage is that reference linking needs more memory for its bookkeeping (three pointers versus only one pointer plus one integer). Also, reference counting should be a bit speedier—when you copy smart pointers, only an indirection and an increment are needed. The list management is slightly more elaborate. In conclusion, you should use reference linking only when the free store is scarce. Otherwise, prefer reference counting.
Does it (
std::shared_ptr
) use a doubly linked list?
All that I could find in the C++ standard was:
20.7.2.2.6 shared_ptr creation
...
7. [ Note: These functions will typically allocate more memory thansizeof(T)
to allow for internal bookkeeping structures such as the reference counts. —end note ]
Which, in my opinion, excludes doubly linked lists, as they do not contain actual count.
Are there any pitfalls for using the
std::shared_ptr
?
Reference management either counting or linking is a victim of the resource leak known as cyclic reference. Let's have an object A that holds a smart pointer to an object B. Also, object B holds a smart pointer to A. These two objects form a cyclic reference; even though you don't use any of them any more, they use each other. The reference management strategy cannot detect such cyclic references, and the two objects remain allocated forever.
Because the implementation of shared_ptr
uses reference counting, cyclic references are potentially a problem. A cyclic shared_ptr
chain can be broken by changing the code so that one of the references is a weak_ptr
. This is done by assigning values between shared pointers and weak pointers, but a weak pointer doesn't affect the reference count. If the only pointers that point to an object are weak, the object is destroyed.
1. Each design feature with multiple implementations if formulated as policy.
2. Smart pointers similarly to pointers that point to object allocated with new
, not only point to that object but also are responsible for its destruction and with the release of the memory it occupies.
3. With no further problems, if no other raw pointers are used and/or point to it.
[1] Modern C++ Design: Generic Programming and Design Patterns Applied. Andrei Alexandrescu, February 01, 2001
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With