Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

C++ Smart pointers

evilrixEngineering Manager
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.
Published:
Updated:

This article is a discussion on smart pointers, what they are and why they are important to C++ programmers. Following the primary discussion I present a simple implementation of a reference counted smart pointer and show a simple example of using it. Although this article does not go into detail about how to develop a reference counted smart pointer the example code at the end is very well commented and that should be enough to aid understanding.


This article is targeted at an intermediate level C++ programmer; however, anyone who develops using C++ (even as a student) would benefit from reading this article and taking to heart the principles it discusses even if you don't follow all of the technical concepts introduced. Not all the terms I use are necessarily explained in this article (I've kept it focused on the core subject); however, anytime a new term is introduced it will be linked to a reference where you can find out more.


Please note, all the code shared in this article is my own; however, during the development of my smart pointer I used the Boost shared_ptr as a basis for the interface to ensure I had captured all the necessary ingredients to provide a fully working smart pointer. If you have access to Boost then, please, do use the range of high quality, peer reviewed smart pointers provided in preference to the one I discuss here. My example, although fully working (and bug free I hope), is meant for educational purposes rather than use in production code and doesn't, for example, implement support for thread safety or intrusive reference counting.


What is a smart pointer? That is a very good question... I'm glad you asked. First, though, allow me to introduce you to my cleaner Raii (pronounced Rye). Her job is to clean up after me. I'm pretty messy, I often get things out and forget to put them back but good old Raii follows me around and when I'm done with something she puts it away for me. Raii can be your cleaner too if you ask her. Isn't she nice? Would you like me to introduce you to her? Yeah? Okay, say hello to Raii or Resource Acquisition is Initialisation to give her her full and rather grand title.


Okay I admit it,  Raii isn't a real person; rather, she's a C++ idiom also, sometimes, referred to as a design pattern. Basically, anytime you allocate a resource you immediately initialise a Raii object with this resource and for the lifetime of the Raii object your resource is accessible but as soon as the Raii object is destroyed it automatically cleans up your resource too. So what does this have to do with smart pointers? Well a smart pointer is just a specialised Raii pattern. It specialises in managing the lifetime of heap allocated memory and will dispose of it when it is destroyed.


So, how does that work? Good question, I am so glad you asked! Let's take a look. Firstly, the following is a very simple example of allocating heap memory in a function.


void foo()
{
   int * pi = new int;
   // Do some work
}


Not much going on there, except did you spot the defect? That's right, the memory isn't deleted when the function ends. See how easy it is to forget? What about this example?


void foo()
{
   int * pi = new int;
   // Do some stuff
   delete pi;
}


Great, memory is deleted no defects there right? Wrong! What if "Do some stuff" throws an exception? When an exception is thrown unless there is a catch handler to handle it the function it is thrown from will immediately exit, the function that called it will also immediately exit if that doesn't have a catch handler and so on until an appropriate catch handler is found or the application terminates. This is called Stack Unwinding. Great, makes sense right? You'd hope so since this is ingrained within the C++ standard!


So, can you see the problem (and I'm not talking about the exception causing the program to terminate, let's assume for the sake of this discussion somewhere further down the stack the exception is caught and handled)? That's right, who deletes the memory allocated in the foo() function? Answer -- no one does! Once again we have a memory leak. So how do we handle this? The obvious solution is to catch the exception, deleted the memory and tr-throw it, right? Ok, let's try that.


void foo()
{
   int * pi = new int;
   try
   {
   // Do some stuff
   }
   catch(...)
   {
	   delete pi;
	   throw;
   }
   delete pi;
}


Great, leak plugged... except... well it's a bit messy isn't it? We now need to have the same pointer deleted twice in one block of code. The general rule of thumb is don't duplicate code as it just makes for extra maintenance. Is there a better way? Well, yes... again we turn to the C++ standard and we find those nice people who provide the language (The C++ Standards Committee) have thoughtfully provided a smart pointer called std::auto_ptr (it lives in the <memory> header file). A smart pointer will automatically delete the memory it is managing once it goes out of scope. How? Well, remember how destructors of classes are automatically called when the class is being destroyed? Well, all that happens is we pass the smart pointer a real pointer that is pointing to heap allocated memory (normally via its constructor, hence the idiom Raii) to manage and when it goes out of scope (and, thus, is destroyed) its destructor deletes the memory for us. Neat eh? So, does this help? Let's see.


#include <memory>
void foo()
{
	// Note, the constructor of auto_ptr is explicit so you MUST use explicit
	// construction (pi = new int; will cause a compilation error)
   
   std::auto_ptr<int> pi(new int); 
   
   // Do some stuff
}


Fantastic, problem solved! No need for catch handlers, no duplicate code and the auto_ptr will automatically delete the memory allocated to it when it goes out of scope, when the foo() function ends. Great, time for a cup of tea and feet up, right? Um, no. You see auto_ptr has a couple of unfortunate problems that can catch out the unwary programmer. Let's take a look.


Problem one: The auto_ptr type can only be used with scalar heap allocations


That's right, when you allocate memory using new and delete you have to use different syntax for scalars vs. arrays. Let's take a look.


	int * pi = new int; // Allocate a scalar
	delete pi; // De-allocate a scalar

	int * pi = new int[10]; // Allocate an array
	delete [] pi; // De-allocate an array


You can't mix up new and delete with new [] and delete []. You must pair them off correctly, otherwise the result is undefined (assume this is bad!). Now auto_ptr is designed specifically to delete scalars.


NB. The C++ Standard provides a better solution for allocating dynamic arrays, it's called a vector.



Problem two: The auto_ptr type has the concept of ownership, also know as move semantics. Let's take a look.


#include <memory>
void foo()
{
   std::auto_ptr<int> pi(new int); 
   
   bar(pi); // this function accepts a std::auto_ptr<int> by value
   
   // Do some stuff using pi
}


The moment you call the bar() function and pass it pi, the ownership of the pointer is passed from the original pi to the one that is within the stack frame of the bar() function. When this function returns ownership is not transferred back to the original pi auto_ptr. What does this mean in simple words? When you assign pi to another auto_ptr ownership is transfered to the new auto_ptr and the original auto_ptr no longer contains a pointer to the memory it was managing, instead it now points to NULL and any attempt to use it after that will result in undefined behaviour (assume this is bad!). This is like giving your mate the money from your wallet and then going to the shops -- you have nothing to pay with so it'll end in tears!


In fact, this is one of the more obvious examples, where it's clear to see what's happening. Imagine the auto_ptr was a member of another class and this was passed by value to another function, unless you're written your own copy-constructor and/or assignment operator (note, you should always implement them in pairs) to perform a safe deep-copy you'll hit the same problem. The auto_ptr member will move ownership to the new copy and the current auto_ptr member will no longer point to valid memory. It's fair to say that auto_ptr can be very dangerous indeed!


Ok, so what do we do now? It's clearly too dangerous to use heap allocation in C++ so we'll all just become .Net managed code programmers right? Eeeek, no! Arrrrr! Quick... it's time for me to introduce our saviour, the reference counted smart pointer. Now, straight off the bat let me say that there is no default implementation of a reference counted smart pointer in C++ (yet) but the one that comes with Boost is ubiquitously use and looks to become part of the next C++ Standard, C++0X.


So what is a reference counted smart pointer? Well, unlike auto_ptr a reference counted smart pointer doesn't transfer ownership. You can copy it as many times as you like and each smart pointer will contain the same pointer to the same object.


How does it work? Well, as well as containing a pointer to the memory it's managing a reference counted smart pointer also contains a pointer to a counter and when you copy it the counter is incremented. The copy will point to the same object being managed and the same counter and when this goes out of scope it will decrement the counter but NOT delete the memory being managed unless the counter indicates this is the last reference. Let me say that again. Every time a copy of the smart pointer is made the counter is incremented and every time a copy goes out of scope the counter it is decremented.


When the counter reaches 0 that means the current copy going out of scope is the last one to reference the pointer being managed so it can safely delete the memory pointed to by the pointer without fear that another smart pointer will try to use it. It's like a bus driver who counts all the people getting on the bus and all the people getting off and when the bus is empty he can park up and have a nice cup of tea.


Still not convinced for the case for using reference counted smart pointers over raw pointers? Think you're too good for them? You never forget to release memory, right? Wow, you're a tough audience! Okay, what's wrong with this then?


class foo
{
public:
	foo() : pi1_(new int), pi2_(new int) {}
	~foo() { delete pi1_; delete pi2_; }
private:
   int * pi1_;
   int * pi2_;
}


Nothing wrong there, right? Wrong! What if the allocation of pi2_ fails? That's ok foo's destructor will delete pi1_ right? Um no! You see destructors aren't called if the construction fails due to exception percolation. Ok, shall we try again? Sure, how about this?


class foo
{
public:
	foo()
	{
		try
		{
			pi1_ = new int;
			pi2_ = new int;
		}
		catch(...)
		{
			delete pi1_;
			delete pi2_;
			throw;
		}
	}
	~foo() { delete pi1_; delete pi2_; }
	
private:
   int * pi1_;
   int * pi2_;
}


Great, no more leaks right? Right! Except... um, now if pi1_ throws you'll try and delete pi2_ as well and since that currently contains an uninitialised value we'll try and delete an invalid pointer and corrupt the heap (this is really bad!). Ok, so we can get around this by initialising both pointers to be NULL in the constructor initialisation list (it's safe to delete NULL) but look what a mess we now have. Imagine if the class was more complex than just these 2 pointers!?


class foo
{
public:
	foo() : pi1_(0), pi2_(0)
	{
		try
		{
			pi1_ = new int;
			pi2_ = new int;
		}
		catch(...)
		{
			delete pi1_;
			delete pi2_;
			throw;
		}
	}
	~foo() { delete pi1_; delete pi2_; }
	
private:
   int * pi1_;
   int * pi2_;
}


Ok, this solves the problem but what a mess? Also, what happens if the re-throw was accidentally omitted from the catch block? Well, construction wouldn't fail (since we blocked its failure) and the destructor will still be called. Meanwhile you'd probably (but not necessarily, sometimes we want to block failure) then try to use a class that wasn't correctly constructed and make a right old jolly mess!


Did someone scream function try block at me? Okay, I'll humour you, let's try again.


class foo
{
public:
	foo()
	try: pi1_(new int), pi2_(new int)
	{
	}
	catch(...)
	{
		delete pi1_;
		delete pi2_;
	}
	
	~foo() { delete pi1_; delete pi2_; }
private:
   int * pi1_;
   int * pi2_;
}


Fantastic, a solution that doesn't use smart pointers! Two things though. First, how many C++ programmers even know about function try blocks? Actually not very many! The syntax can be quite confusion for someone who isn't well versed in them. Second, the catch block in this example never re-throws (just like I discussed above) so this constructor can't fail then right? Oh if only the C++ standard were that simple and consistent. The answer is (come on you knew I was going to say this right?) yes it most certainly can!


You see with a constructor try block if an exception is thrown during construction it will always (yes, that's right, always) percolate it even if you don't re throw it yourself. Actually, that's a good thing normally, but not always. You might have a valid case to not re-throw, the exception might not necessarily mean construction failure. The only way to solve this is to nest yet another try block. Wow, what a tangled web we weave.


Ok, let's look at the very simple way to solve this using smart pointers.


class foo
{
public:
	foo() : pi1_(new int), pi2_(new int) {}

private:
	// These are reference counted (soon to be discussed below)
   smart_ptr<int> pi1_;
   smart_ptr<int> pi2_;
}


Wow, now that is simple. Each smart pointer is allocated a memory to manage and when the class goes out of scope each will delete that memory. If the construction fails any memory allocated will be correctly deleted and if the smart pointer was never constructed because the object being constructed before it throws an exception it'll never try and delete memory that it was never constructed to start with. Simple eh?


What I'd like to do now is to introduce you to my very own hand crafted reference counted smart pointer. Now it's important to note that although the following code is a fully functional, it is really for educational purposes only. It has not been tested in a production environment (I wrote it just for this article) and if you do decide to heed the advice and use smart pointers please do refer to the Boost implementation as your first port of call.


So, without further ado, here she is in all her majestic glory (*cough*)...


// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Basic smart pointer for scalar and array types
// Note: this class is not thread safe
// evilrix 2009
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This functor will delete a pointer to a scalar object
template <typename ptrT>
struct delete_scalar_policy
 {
    void operator()(ptrT * & px) { delete px; px = 0; }
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This functor will delete a pointer to an array of object
template <typename ptrT>
struct delete_array_policy
 {
    void operator()(ptrT * & px) { delete[] px; px = 0; }
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This is the smart pointer template, which takes pointer type and
// a destruct policy, which it uses to destruct object(s) pointed to
// when the reference counter for the object becomes zero.
template <
    typename ptrT,
    template <
    typename
    > class delete_policy
 >
class smart_ptr
 {
private:
    typedef delete_policy<ptrT> delete_policy_;
    typedef void (smart_ptr::*safe_bool_t)();
    typedef int refcnt_t;
 
public:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Make a nice typedef for the pointer type
    typedef ptrT ptr_type;
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // dc_tor, c_tor and cc_tor
    smart_ptr() : px_(0), pn_(0) {}
 
    explicit smart_ptr(ptr_type * px) :
       px_(px), pn_(0) {
       pn_ = new int(1);
    }
 
    smart_ptr(smart_ptr const & o) :
       px_(o.px_), pn_(o.pn_) {
       ++*pn_;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // d_tor, deletes the pointer using the destruct policy when the
    // reference counter for the object reaches zero
    ~smart_ptr()
    {
       try
       {
          if(pn_ && 0 == --*pn_)
          {
             delete_policy_()(px_);
             delete pn_;
          }
       }
       catch(...)
       {
          // Ignored. Prevent percolation during stack unwinding.
       }
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Assignment operator copies an existing pointer smart_ptr, but
    // in doing so will 'reset' the current pointer
    smart_ptr & operator = (smart_ptr const & o)
    {
       if(&o != this && px_ != o.px_)
       {
          reset(o.px_);
          pn_ = o.pn_;
          ++*pn_;
       }
 
       return *this;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Performs a safe swap of two smart pointer.
    void swap(smart_ptr & o)
    {
       refcnt_t * pn = pn_;
       ptr_type * px = px_;
 
       pn_ = o.pn_;
       px_ = o.px_;
 
       o.pn_ = pn;
       o.px_ = px;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Resets the current smart pointer. If a new pointer is provided
    // The reference counter will be set to one and the pointer will
    // be stored, if no pointer is provided the reference counter and
    // pointer wil be set to 0, setting this as a null pointer.
    void reset(ptr_type * px = 0)
    {
       smart_ptr o(px);
       swap(o);
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Returns a reference to the object pointed too
    ptr_type & operator * () const { return *px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Invokes the -> operator on the pointer pointed too
    // NB. When you call the -> operator, the compiler  automatically
    //     calls the -> on the entity returned. This is a special,
    //     case, done to preserve normal indirection semantics.
    ptr_type * operator -> () const { return px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Get the pointer being managed
    ptr_type * get() const { return px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Conversion to bool operator to facilitate logical pointer tests.
    // Returns a value that will logically be true if get != 0 else
    // and value that is logically false. We don't return a real
    // bool to prevent un-wanted automatic implicit conversion for
    // instances where it would make no semantic sense, rather we
    // return a pointer to a member function as this will always
    // implicitly convert to true or false when used in a boolean
    // context but will not convert, for example, to an int type.
    operator safe_bool_t () const { return px_ ? &smart_ptr::true_ : 0; }
 
private:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // A dummy member function used to represent a logically true
    // boolean value, used by the conversion to bool operator.
    void true_(){};
 
private:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Poiners to the object being managed and the reference counter
    ptr_type * px_;
    refcnt_t * pn_;
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Facility class to simplify the creation of a smart pointer that
// implements a 'delete scalar policy'.
template <typename ptrT>
struct scalar_smart_ptr
 {
    typedef smart_ptr<ptrT, delete_scalar_policy> type;
private: scalar_smart_ptr();
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Facility class to simplify the creation of a smart pointer that
// implements a 'delete array policy'.
template <typename ptrT>
struct array_smart_ptr
 {
    typedef smart_ptr<ptrT, delete_array_policy> type;
private: array_smart_ptr();
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


You'll see that I have fully documented the code with in-line comments so I won't be repeating myself here regarding the specifics of how she's implemented. Instead, let's take a look at her in action.


#include <devtools/smart_ptr.hpp>

using namespace devtools;

int main()
{
	// This smart pointer will manage a pointer to a scaler
	scalar_smart_ptr<int>::type pi_scalar(new int);

	// This smart pointer will manage a pointer to an array
	array_smart_ptr<int>::type pi_array(new int[10]);
}


Pretty simple eh? In fact, it's as simple to use as auto_ptr except it solves both problems one and two discussed above, it can handle pointers to scalars and arrays and it can be copied as many times as you like without fear of the current pointer losing ownership.


So, what have we learnt? Well, heap memory management isn't as simple in C++ as one would hope and that the tools provided by the (current) C++ standard are wholly inadequate. Trying to write code without using smart pointers if you are allocating heap memory is a recipe to disaster (or at the very least a debugging nightmare waiting to happen). Finally, we discovered there is a solution in the form of the reference counted smart pointer and although the current C++ standard has no such concept right now it is coming but, meanwhile, you can use the excellent ones provided by Boost or roll your own -- it's not really that complicated.


I hope you enjoyed reading this article and that it has convinced you to always use the Raii pattern and smart pointers in your code. if you liked this article please don't forget to vote "yes" for it.



My thanks to:

  • TruthHunter for identifying and reporting a small error in the destructor where  I wasn't checking pn_ was null before dereferencing it. Fixed.
  • Rulsan Burakov for identifying a typo for one of the typedefs. Fixed.
20
13,480 Views
evilrixEngineering Manager
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.

Comments (14)

evilrixEngineering Manager
CERTIFIED EXPERT

Author

Commented:
>> Wonderful article!
Thanks for your kind words.

>> Is there anyway I can grade or give you points for this ?!?
You can vote "yes" above, top right just underneath the article where it says, "Was this article helpful?".
Hi,

First of all, my sincere and heartfelt gratitude for the well-written education.  (I added my "yes" vote.) I just wanted to ask a quick question about the smart_ptr implementation, in case it would help someone else.  Should the smart_ptr destructor be modified to check that the pn_ member is not 0/NULL before dereferencing it?

I ask because I encountered a problem when I tried to do the following;

- Declared a smart_ptr but didn't initialize it.  The smart_ptr constructor simply initialized the pn_ (and px_) member to 0.
- Declared and initialized a second smart_ptr.
- Assigned the second smart_ptr to the first, via operator=.  The operation invoked reset() and then swap(), which created a temp. smart_ptr which swapped members with the original smart_ptr (receiving its NULL pn_ and px_ members).  The temp. smart_ptr then went out of scope, at which point its destructor tried to dereference its NULL pn_ member.

Adding a check for pn_ != 0 before dereferencing seems to have fixed the problem, but I would welcome any additional help or suggestions.  Thanks again for the help!!!
evilrixEngineering Manager
CERTIFIED EXPERT

Author

Commented:
Should the smart_ptr destructor be modified to check that the pn_ member is not 0/NULL before dereferencing it?

Good spot. I've applied a fix to the article and credited you with spotting that.

Thank you.

typedef delete_scalar_policy<ptrT> delete_policy_;
here probably it should be destruct_policy.
evilrixEngineering Manager
CERTIFIED EXPERT

Author

Commented:
Good spot. Fixed and added a credit to the article. Thanks.

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.