Today I’d like to show you the feature boost libraries provides I do like best.
Smart Pointers are much more smarter than usual pointers - althougth they are not more complicated.
Boost smart pointer library provides five smart pointer classes which behave much like built-in C++ pointers except that they automatically delete the object pointed to at the appropriate time.
- scoped_ptr - (scoped_ptr.hpp)
The scoped_ptr class template stores a pointer to a dynamically allocated object. (Almost) everything the object pointed to is guaranteed to be deleted, either on destruction of the scoped_ptr, or via an explicit reset.
- scoped_array - [scoped_array.hpp]
Almost the same as scoped_ptr but it stores to a dynamically allocated array. It cannot correctly hold a pointer to a single object. See scoped_ptr for that usage.
- shared_ptr - [shared_ptr.hpp]
shared_ptr can be used in C++ Standard Library containers.
- shared_array - [shared_array.hpp]
Almost the same as shared_ptr but for stores to a dynamically allocated array. Can be used in STL containers.
- weak_ptr - [weak_ptr.hpp]
The weak_ptr class template stores a “weak reference” to an object that’s already managed by a shared_ptr. To access the object, a weak_ptr can be converted to a shared_ptr.
- intrusive_ptr - [intrusive_ptr.hpp]
A light pointer with an embedded reference count. Every new intrusive_ptr instance increments the reference count.
The scoped_ptr class is the simplest one, it just ensures deletion when a pointer becomes invalid because of being no longer needed.
Instead of the usual way…
void Example()
{
CMyClass* pPtr(new CMyClass);
if (!pPtr->Call())
{
delete pPtr;
return;
}
pPtr->Use();
delete pPtr;
}
…you can do it like this:
void Example()
{
boost::scoped_ptr<CMyClass> pPtr(new CMyClass);
if (!pPtr->Call())
return;
pPtr->Use();
}
Almost everything concerning memory management of the object is done automatically. You don’t have to care about - this can reduce the error rate a lot. The loss of performance is mostly not really noticeable - it’s about the same fast/slow as normal new/delete.
It’s not possible to use it for STL container elements, more than one pointer on an object, or other allocators than new and delete.
If you need more than one smart pointer to an object, you have to define a criterion when the object is no longer needed and can be destroyed.
That’s what shared_ptr is for. A reference counter holds the number of pointers that have a reference to one object. Every object has it’s own counter. If a new pointer is added to the object, the counter increments and when a destructor is called it decrements. If the counter becomes zero the objects at once gets deleted.
void Example()
{
boost::shared_ptr<CMyClass> pPtr1(new CMyClass);
boost::shared_ptr<CMyClass> pPtr2= myclass;
pPtr1.reset();
}
An object (in my testcase an object of CMyClass) gets stored on the heap and pPtr1 points on it. Later another pointer points on it. Then pPtr1 gets reset (same as set to NULL). When the last existing reference of CMyClass gets invalid the object will be deleted.
Whenever you need to have a container of pointers to heap-allocated objects, there is usually only one exception-safe way: to make a container of smart pointers like boost’s shared_ptr.
Many container classes (e.g. STL container) need a copy-constructor for adding existing elements to the object list. When storing complex classes you usually use an container of pointers.
std::vector<CMyClass*> list;
list.push_back( new CMyClass(333,"sample") );
list.push_back( new CMyClass(666,"word") );
A disadvantage of that is that you have to care yourself about the deletion of objects.
For cases like that boost provides a shared_ptr template class.
typedef boost::shared_ptr<CMyClass> CMyClassPtr;
std::vector<CMyClassPtr> list;
list.push_back( CMyClassPtr(new CMyClassPtr(333,"sample")) );
list.push_back( CMyClassPtr(new CMyClassPtr(666,"word")) );
Now the stored objects in the list get freed (deleted) with the list.
It’s also possible to use shared_ptr for void. It can act as a generic object pointer similar to void*. A shared_ptr can be handled much the same manner as a raw void* which is used to temporarily strip type information from an object pointer.
When using shared_ptr you might notice that they can be big and slow ’cause every shared_ptr needs it’s own tracker. To get rid of performance loss your choice should be intrusive_ptr which is something like the lightweight version of shared_ptr but you have to write the counter mechanism yourself.
In boost mailinggroups I found a thread-save implementation that is also compatible to the rest of the boost standard.
void intrusive_ptr_add_ref(CRefCounted * p)
{
_InterlockedIncrement(&(p->references));
}
void intrusive_ptr_release(CRefCounted * p)
{
if (_InterlockedDecrement(&(p->references)) == 0)
delete p;
}
Important to know is that boost differs between strong and weak pointers. shared_ptr stands for a strong pointer ’cause it avoids deletion ahead of time.
The weak_ptr is, as the name says, the implementation for a weak pointer. A weak reference can be get from a strong reference. If the strong does no more exist to that time the weak reference is just 0.
shared_ptr<double> pStrong(new double(5.562));
weak_ptr<double> pWeak(pStrong);
if(double* reference = pWeak.get())
{
reference->SomeFunc();
}
A weak_ptr does not provide any accessing method or operator (operator->, or get()-method).
To convert a weak_ptr to shared_ptr you can use the lock()-method.
weak_ptr<X> target;
if(shared_ptr<X> pPtr1 = target.lock())
{
pPtr1->SomeFunc();
}
else
{
// target lost (e.g. expired), acquire a new one
}
Things you’d know about smart pointers:
- No circular references
If you have two objects referencing each other through a reference counting pointer, they are never deleted. To break those circle the usage of weak_ptr is recommented.
- Assign a newly constructed instance to a smart pointer immediately
Instances should always get assigned to a smart pointer at once. An assigned smart pointer holds an object that you don’t have to care once again. This helps to not accidentally delete an object that is still referenced by a smart pointer. When not doing this it can end up with an invalid reference count.
- Convert carefully
Always be carful with convertion between real pointers and smart pointers. Smart pointers are no more real pointers.
If you keep in mind these points nothing should go fatally wrong.
use_count returns the number of shared_ptr(s) that point on an object. Besides the detection whether an object is needed anymore and maybe can be destroyed, it has the possibility to verify that an object is valid. However it’s just provided as a testing and debugging aid rather than for use in productive code.
if(pObj.use_count() > -1) { oObj->MakeMeHappy() }
The < operator doesn’t compare the stored pointers, instead it tells you about the ownership.
It enables that you can question if p1 shares ownership with p2.
A valid expression is:
if( !(p1 < p2) && !(p2 < p1) ) {}
You can have diverse reason why using smart pointers. Some common reasons can be:
- Bug prevention
As so often the prevention of bugs justifies the effort of bringing in a new technique. Automatic cleanup reduces the chance that the programmer forgets about cleanup an object.
- No need for NULL
The constructor of smart pointer initializes the pointer to NULL.
- Exception savety
Imagine a function called from a dynamic generated object fails during run progress. In most cases the object from where the function was called never gets deleted. If we’re lucky, this leads only to memory leaks.
- Smart garbage collector
C++ in its standard doesn’t have a garbage collector. Smart pointers can be used for that purpose.
Link:
Smart Pointers Overview on Boost C++ Libraries Website