Reference-Counting Smart Pointers made Surprisingly Simple

Why Do I Need Smart Pointers?

Working with dynamically allocated memory in languages like C++ can be difficult. If you allocate some memory and forget to free it, you get a memory leak. But, if you free it more than once, you get memory corruption. Not surprisingly, memory management errors are a significant source of bugs in many C++ programs.

Now imagine if you could dynamically allocate memory and use it in your program without ever worrying about freeing it! No, I’m not suggesting you switch to Java. You can obtain this ease-of-use in C++ too, simply by using smart pointers.

How Do Smart Pointers Work?

The fundamental insight behind smart pointers is that the stack is safer than the heap. Variables on the stack are automatically destructed and freed when they go out of scope. Variables on the heap are freed only by an explicit call to delete. The trick is to represent a heap-based variable, accessed through a traditional dumb pointer, by a stack-based variable. Smart pointers accomplish this sleight-of-hand by encapsulating a dumb pointer in an object that is kept on the stack. When this object goes out of scope, its destructor takes care of freeing the memory associated with the underlying dumb pointer.

Reference-counting smart pointers take this technique a step further and track all the copies of the smart pointer object. This enhancement handles the aliasing problem, when more than one object or piece of code contains copies of the same pointer. Only when the last copy of the object goes out of scope is the underlying dumb pointer freed.

Smart Pointers in Action

Some concrete examples should help to show you how reference-counting smart pointers work. Smart pointers in C++ are typically implemented as template classes. This example uses the SuperSmartPointer template class from Chapter 25 of Professional C++. Feel free to download SuperSmartPointer.h and SuperSmartPointer_in.h and play around with it. In this post, I won’t get into the implementation details of the SuperSmartPointer – just treat it as a black box for now.

The examples that follow use a simple class that just outputs a line in its constructor and destructor and provides one other public method:


class Nothing
{
public:
   Nothing() { cout << "ctor\\n"; }
   ~Nothing() { cout << "dtor\\n"; }
   void SaySomething() { cout << "Something\\n"; }
};

First, consider the following function:


void TestNothing()
{
   Nothing \*myNothing = new Nothing();
   myNothing->SaySomething();
}

The output of a program calling the function is:


ctor
Something

One call to the constructor, but no calls to the destructor. Memory leak! The function never calls delete on the myNothing pointer.

Now let’s use the SuperSmartPointer:

void TestNothing()
{
   Nothing \*myNothing = new Nothing();
   SuperSmartPointer<Nothing> mySmartNothing(myNothing);
   mySmartNothing->SaySomething();
}

Note that mySmartNothing is declared as a stack-based object. The syntax is a bit confusing because SuperSmartPointer is a template class, and so requires the pointer type Nothing to be specified in the <> angle brackets. For more detail on class templates, see this tutorial, or Chapter 11 of Professional C++.

When the mySmartNothing object goes out of scope, its destructor is called, which frees the myNothing pointer. The output of the program calling this function now is:


ctor
Something
dtor

No memory leak!

In addition to showing you how using a smart pointer can prevent a memory leak, the example above showed you how to use a smart pointer to get at the memory location to which it points. As you saw, you can dereference SuperSmartPointer objects with -> just as you would dereference normal dumb pointers. You can also use \*. That's because the SuperSmartPointer template class uses C++ operator overloading to provide those dereferencing operators.

Reference-Counting Smart Pointers in Action

The example above barely scratches the surface of the power of reference-counting smart pointers. Consider a program that passes a pointer between various functions:


void FunctionB(Nothing \*myNothing)
{
   // do something
}

Nothing \*FunctionC(Nothing \*myNothing)
{
   // do something
   return (new Nothing());
}

void FunctionA()
{
   Nothing \*myNothing = new Nothing();
   // do something
   FunctionB(myNothing);
   myNothing = FunctionC(myNothing);
}

The output from this program is:

ctor
ctor

Two object constructions and no destructions! The problem, of course, is that the program never calls delete on the objects it creates, perhaps because it isn’t clear which function is responsible for freeing the memory.

Now let’s look at a revised version using SuperSmartPointer:


void FunctionB(SuperSmartPointer<Nothing> mySmartNothing)
{
   // do something
}

SuperSmartPointer<Nothing>
FunctionC(SuperSmartPointer<Nothing> mySmartNothing)
{
   // do something
   SuperSmartPointer<Nothing> myNewSmartNothing(new Nothing());
   return (myNewSmartNothing);
}

void FunctionA()
{
   Nothing \*myNothing = new Nothing();
   SuperSmartPointer<Nothing> mySmartNothing(myNothing);
   // do something
   FunctionB(mySmartNothing);
   mySmartNothing = FunctionC(mySmartNothing);
}

Notice that there are no explicit calls to delete in this version either. However, the output of the program is:


ctor
ctor
dtor
dtor

No memory leaks! That’s because the dumb pointers are encapsulated by smart pointer objects on the stack. When the last smart pointer object for a given dumb pointer goes out of scope, its destructor frees the underlying dumb pointer.


When using smart pointers, always pass the smart pointer object by value, not by reference, so that the reference count of the underlying dumb pointer can be incremented. If you try to pass the smart pointer by reference, such as by playing with a pointer to the smart pointer object, the reference counting will get confused and won’t work properly.

Conclusion

Hopefully the above examples showed you some of the power of reference-counting smart pointers. I encourage you to try them out in your next C++ program!

Comments:

that was a good tutorial put as simple as possible.. would like to see more post similar to this!

Posted by tarakeshwar on May 17, 2007 at 04:05 AM MDT #

Hi I found this article of yours to be quite useful. Could you also provide a tutorial about the design of any fsm using c++.

Posted by Avinash Shah on May 17, 2007 at 03:31 PM MDT #

If I'm not mistaken, circular references break reference counting. Here is an example of what I mean:
class Foo {
private:
  SuperSmartPointer<Foo> _foo;
public:
  SetPtr(SuperSmartPointer<Foo> foo)
  { _foo = foo; }
  ~Foo() { cout<<"dtor"<<endl; }
};

void FunctionA() {
  SuperSmartPointer<Foo>
    foo1(new Foo()), foo2(new Foo());
  foo1->SetPtr(foo2);
  foo2->SetPtr(foo1);
}
In this example, the destructors are never called. Suppose foo2 pops off the stack first. foo1 has a smart pointer to it, so it can't be deallocated. Next, foo2 can't be deallocated because foo1 (which still exists) has a smart pointer to it.

Posted by Cornelius P. Porifera on June 16, 2007 at 06:05 AM MDT #

Thank you for the article, it was helpful. I would recommend that you rethink the CSS for you code sections though. The font is unreadably small.

Posted by Brian on October 17, 2007 at 07:18 AM MDT #

Post a Comment:
Comments are closed for this entry.
About

Nick Solter is a software engineer and author living in Colorado.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today