Smart pointers
Unique ptr vs Shared ptr:
Both of these classes are smart pointers, which means that they automatically (in most cases) will deallocate the object that they point at when that object can no longer be referenced. The difference between the two is how many different pointers of each type can refer to a resource.
When using unique_ptr
, there can be at most one unique_ptr
pointing at any one resource. When that unique_ptr
is destroyed, the resource is automatically reclaimed. Because there can only be one unique_ptr
to any resource, any attempt to make a copy of a unique_ptr
will cause a compile-time error. For example, this code is illegal:
unique_ptr<T> myPtr(new T); // Okay
unique_ptr<T> myOtherPtr = myPtr; // Error: Can’t copy unique_ptr
However, unique_ptr
can be moved using the new move semantics:
unique_ptr<T> myPtr(new T); // Okay
unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr
Similarly, you can do something like this:
unique_ptr<T> MyFunction() {
unique_ptr<T> myPtr(/* … */);
/* … */
return myPtr;
}
This means “I’m returning a managed resource to you. If you don’t explicitly capture the return value, then the resource will be cleaned up. If you do, then you now have exclusive ownership of that resource.” In this way, you can think of unique_ptr
as a safer, better replacement for auto_ptr
.
shared_ptr
, on the other hand, allows for multiple pointers to point at a given resource. When the very last shared_ptr
to a resource is destroyed, the resource will be deallocated. For example, this code is perfectly legal:
shared_ptr<T> myPtr(new T); // Okay
shared_ptr<T> myOtherPtr = myPtr; // Sure! Now have two pointers to the resource.
Internally, shared_ptr
uses reference counting to track how many pointers refer to a resource, so you need to be careful not to introduce any reference cycles.
In short:
- Use
unique_ptr
when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. - Use
shared_ptr
when you want multiple pointers to the same resource.It is bothcopyable
andmovable.
auto_ptr
auto_ptr is most charitably characterized as a valiant attempt to create a unique_ptr before C++ had move semantics. auto_ptr is now deprecated, and should not be used in new code.
If you have auto_ptr in an existing code base, when you get a chance try doing a global search-and-replace of auto_ptr to unique_ptr; the vast majority of uses will work the same, and it might expose (as a compile-time error) or fix (silently) a bug or two you didn’t know you had.
No two auto_ptr objects should own the same element, since both would try to destruct them at some point. When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is set to no longer point to the element.
auto_ptr<T> pt( new T );
unique_ptr<T> pt( new T );
Now unique pointer/smart pointer owns this pointer.
Weak_Ptr
weak_ptr
points to a shared_ptr
but does not increase its reference count.This means that the underying object can still be deleted even though there is a weak_ptr
reference to it.
The way that this works is that the weak_ptr
can be use to create a shared_ptr
for whenever one wants to use the underlying object. If however the object has already been deleted then an empty instance of a shared_ptr
is returned. Since the reference count on the underlying object is not increased with a weak_ptr
reference, a circular reference will not result in the underlying object not being deleted.
Deciding what smart pointer to use
is a question of ownership. When it comes to resource management, object A owns object B if it is in control of the lifetime of object B. For example, member variables are owned by their respective objects because the lifetime of member variables is tied to the lifetime of the object. You choose smart pointers based on how the object is owned.
If you have sole ownership of the object, use std::unique_ptr<T>
.
If you have shared ownership of the object…
– If there are no cycles in ownership, use std::shared_ptr<T>
.
– If there are cycles, define a “direction” and use std::shared_ptr<T>
in one direction and std::weak_ptr<T>
in the other.
If the object owns you, but there is potential of having no owner, use normal pointers T*
(e.g. parent pointers).
If the object owns you (or otherwise has guaranteed existence), use references T&
.
Be aware of the costs of smart pointers. In memory or performance limited environments, it could be beneficial to just use normal pointers with a more manual scheme for managing memory.
how cyclic data structures makes reference count above zero.How the problem is solved by weak_ptrs?
The problem occurs with C++ code like this (conceptually):
class A { shared_ptr<B> b; … };
To answer the second part of your question: It is mathematically impossible for reference counting to deal with cycles. Therefore, a weak_ptr
(which is basically just a stripped down version of shared_ptr
) cannot be used to solve the cycle problem – the programmer is solving the cycle problem.
class B { shared_ptr<A> a; … };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // +1
// Ref count of ‘x’ is 2.
// Ref count of ‘x->b’ is 1.
// When ‘x’ leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)
To solve it, the programmer needs to be aware of the ownership relationship among the objects, or needs to invent an ownership relationship if no such ownership exists naturally.
The above C++ code can be changed so that A owns B:
class A { shared_ptr<B> b; … };
class B { weak_ptr<A> a; … };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // No +1 here
// Ref count of ‘x’ is 1.
// Ref count of ‘x->b’ is 1.
// When ‘x’ leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of ‘x->b’ will drop to 0.
// So both A and B will be deallocated.
A crucial question is: Can weak_ptr
be used in case the programmer cannot tell the ownership relationship and cannot establish any static ownership because of lack of privilege or lack of information?
The answer is: If ownership among objects is unclear, weak_ptr
cannot help. If there is a cycle, the programmer has to find it and break it. An alternative remedy is to use a programming language with full garbage collection (such as: Java, C#, Go, Haskell), or to use a conservative (=imperfect) garbage collector which works with C/C++ (such as: Boehm GC).
Define your own generic smart pointer:
class RC
{
private:
int count; // Reference count
public:
void AddRef()
{
// Increment the reference count
count++;
}
int Release()
{
// Decrement the reference count and
// return the reference count.
return –count;
}
};template < typename T > class SP
{
private:
T* pData; // pointer
RC* reference; // Reference count
public:
SP() : pData(0), reference(0)
{
// Create a new reference
reference = new RC();
// Increment the reference count
reference->AddRef();
}
SP(T* pValue) : pData(pValue), reference(0)
{
// Create a new reference
reference = new RC();
// Increment the reference count
reference->AddRef();
}
SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
{
// Copy constructor
// Copy the data and reference pointer
// and increment the reference count
reference->AddRef();
}
~SP()
{
// Destructor
// Decrement the reference count
// if reference become zero delete the data
if(reference->Release() == 0)
{
delete pData;
delete reference;
}
}
T& operator* ()
{
return *pData;
}
T* operator-> ()
{
return pData;
}
SP<T>& operator = (const SP<T>& sp)
{
// Assignment operator
if (this != &sp) // Avoid self assignment
{
// Decrement the old reference count
// if reference become zero delete the old data
if(reference->Release() == 0)
{
delete pData;
delete reference;
}
// Copy the data and reference pointer
// and increment the reference count
pData = sp.pData;
reference = sp.reference;
reference->AddRef();
}
return *this;
}
};
Related posts:
Value semantics vs reference sementics