[ Summary ]        [ Download Files ]        [ News ]        [ Forums ]        [ Bugs ]

SourceForge.net Logo


 

      Table of Contents:

 

Description of the Problem

In C++, pointers are very commonly used, but the current "built-in" pointers leave something to be desired. When a built-in pointer is created, it is not automatically set to NULL. If an uninitialized pointer is then compared to NULL (pointer == NULL) the test will pass, and any dereferencing will result in undefined behavior. It's fairly easy to remember to set a pointer to NULL when you create it, so this issue isn't that important, but what if you call a function that returns a pointer? If the memory was allocated on the heap (i.e. came from a call to new or malloc) then someone has to delete it, or it will be a memory leak. It's up to the programmer to read the documentation and figure it out. What about in a multi-threaded environment? It's very easy for two threads to share the same data, but what if both of them are using the same pointer, and one thread calls delete on the pointer while the other thread is still using it? Finally, if you're using exceptions, you've probably had a pretty hard time making sure that each time an exception is thrown, all of the allocated memory gets freed. Take the following code:

try
{
    int* pInt = new int;
    SomeFunction();
    .
    .
    .
    delete pInt;
}


What if SomeFunction throws an exception? You have to make sure that each and every exception that SomeFunction (or any function called by SomeFunction) throws will be caught, in order to delete pInt; otherwise you'll have a memory leak. Certainly there must be a better solution than using these built-in "dumb" pointers. Well, there is, they're called "Smart Pointers", and they handle all of this stuff for you.

 

Solution to the Problem

The idea behind this Smart Pointer implementation is to have a set of templated objects that wrap the functionality of a built-in pointer. These Smart Pointers either "point" to an object or they equal NULL (they never point to memory that has been deleted and they are always initialized). Here are some of the goals that I had for this project:

  1. The pointers must always point to valid memory, or be NULL

  2. The pointers will be reference counted and handle freeing the memory being pointed to (so they can be exception safe while at the same time eliminating memory leaks)

  3. The pointers should be as similar to the built-in pointers as possible

In order to accomplish goal 1, the pointers had to be thread safe, which usually means platform specific, but this implementation will work on both Win32 and POSIX compliant (Linux, BSD, Solaris, Mac OS X, etc.) Operating Systems. No matter how many threads you have, your pointers will never point to invalid memory.

Goal 2 required that the pointers be exception safe. When an exception is thrown, the stack unwinds until it finds some code that will catch the exception. As the stack is unwinding, the destructors of each object are called. When the Smart Pointer destructor is called, it will remove one reference from the count associated with the object it is pointing to. If the new reference count is zero, that object will be deleted. So, if an exception is thrown, as long as a Smart Pointer is pointing to the object the was allocated on the heap, everything will be fine and the memory will be freed.

The third goal was particularly difficult to accomplish. To do this, I had to implement implicit casting (casting without a casting operator), explicit casting through casting operators like dynamic_cast and reinterpret_cast, and the pointers had to be "const" correct.

Additionally, in order for the smart pointers to act like the built-in types, it was  necessary for the implementation to be non-intrusive. An intrusive smart pointer is one that requires a common base class be used in any object that will be pointed to. When you create your custom objects, you would have to inherit from a base class the gives reference counting functionality to your object. This approach only works for user defined types, and the Smart Pointers would never be able to point to built-in types (like int and float). A non-intrusive approach requires no changes to a defined type (whether built-in or user defined) because the pointer has a more intelligent means of keeping track of the reference count.

Along with reference counted pointers comes the ugly problem of circular references. A circular reference is when two reference counted objects are keeping each other "alive" even though neither is still in use. For example, two objects are defined, one which is a Parent object and the other a Child object. As a member variable, the Parent object has a Smart Pointer pointing to the Child objects, while the Child has a Smart Pointer pointing to the Parent. The reference counts will never get to zero, and therefore the memory will never get deleted. This problem has been solved through the use of Inactive Pointers, which I will explain later.

All of the above goals have been met by this implementation. The programmer doesn't have to worry about memory management (never see the word "delete" again!), the pointers are copy safe, thread safe, exception safe, and they never point to memory that has already been deleted. If used properly, they avoid circular references, and they work almost identically to the built-in "dumb" pointers.

 

How to use the Smart Pointers

To use the smart pointers in a C++ source file, just add the following line of code:
#include "Smart Pointers.h"
That's it. You don't have to add any files to a project, or any dependencies to a makefile (unless you plan on changing the Smart Pointer code). Everything that you need is included in that file, but you still need more info than that. Here are the two main Smart Pointers which are defined:

Ptr< type >        // also known as "Active Pointer"

Here is some sample code:

class Base{};
class Derived : public Base{};

...

Ptr<Derived> pDerived;           // Smart Pointer do Derived object;
                                 // initialized to NULL

Ptr<const Base> pcBase;          // Smart Pointer to const Base object;
                                 // object pointed to can't be modified

const Ptr<int> pInt( new int );  // const pointer, can't be 
                                 // reassigned to point to
                                 // something else

pDerived = new Derived;          // create a Derived object
pCBase = pDerived;               // implicit cast

pDerived.ResetPtr();             // same as  pDerived = NULL;

                                 // pcBase still points to the object
                                 // created by new operator

// no need to call delete !!!

 

InactivePtr< type >        // also known as "Inactive Pointer" or "Passive Pointer"

Here is some sample code:

InactivePtr<int> ppInt;            // Inactive Pointer to int;
                                   // initialized to NULL

{

Ptr<int> pTempInt = new int;   // create a new int
ppInt = pTempInt;              // set the Inactive Pointer
                               // to point to the new int

}   // the active pointer will go out of scope, destructing
    // the int and setting the Inactive Pointer to NULL

Ptr<int> pActiveInt( ppInt );      // create an Active Pointer
                                   // using an Inactive Pointer

if( pActiveInt != NULL )           // you must always check an Active
{                                  // Pointer for NULL if it was created
    ...                            // from an Inactive Pointer; in this
}                                  // case it will be equal to NULL

 

Casting Operators

The Smart Pointer casting operators work almost exactly like the C++ style casting operators. Just in case you don't remember the C++ casting operators, here's a quick review:

reinterpret_cast< type >( expression )

const_cast< type >( expression )

static_cast< type >( expression )

dynamic_cast< type >( expression )

So that's the C++ style casting operators. The Smart Pointer specific versions are quite similar, with one major exception. In the C++ operators, if you want to cast from a pointer to a pointer, you must specify that in the type field. For example:

int* pInt = dynamic_cast<int*>( SomePointerToInt );

The * symbol is in between the less than-greater than symbols is a necessity. In the Smart Pointer version, it is assumed that you are casting from one pointer to another, so the * symbol is eliminated (basically, I couldn't get it to work any other way):

Ptr<int> pInt = Dynamic_Cast<int>( SomeSmartPointerToInt );

Also, you'll notice the the 'D' and 'C' are capitalized. The casting operators can't be overloaded, so I had to come up with similar names:
Reinterpret_Cast
Const_Cast
Dynamic_Cast
(You'll notice that there is no Static_Cast. Since there is no reason to use static_cast on built-in pointers, there isn't a reason to have a Smart Pointer version.)

There are two ways that a Smart Pointer can be const:
    1) the pointer itself is const and can't be reassigned
        ex: const Ptr<int>
    2) the object being pointed to is const and can't be modified
        ex: Ptr<const int>
Const_Cast is only useful in casting away the constness of the second example.

All of the Smart Pointer casts work regardless of which type of Smart Pointer is being used (i.e. Active to Active, Active to Inactive, etc.), except for Dynamic_Cast. You can only use it to cast from Active Pointer to Active Pointer, or from Active Pointer to Inactive Pointer. The reason for this has to do with the unstable nature of Inactive Pointers. (If Dynamic_Cast used thread synchronization constructs, then this function could work, but that would be a pain to implement correctly, since Dynamic_Cast is not part of an object. If there are enough requests for this though, I'll implement it.)

 

Thread Safety

When I say that the Smart Pointers are "thread safe" it means that they will work correctly in a multi-threaded environment. The important thing to realize is that the Smart Pointers, and only the Smart Pointers, are thread safe. The objects that are being pointed to, are not thread safe. If multiple threads are accessing the same object through two different Smart Pointers, the pointers will still hold the guarantee of pointing to valid memory, but the object is not thread safe, it is up to the programmer to do this.

Nevertheless, there are a few tips to ensuring that the Smart Pointers themselves remain thread safe. First, if you always pass the Smart Pointers by value, then you'll never have to worry about anything. Since the Smart Pointers are reference counted, they will take care of everything on their own, but what if you have to pass a pointer into a function, and the function will change what the pointer points to? In C you would use a pointer-to-a-pointer, and in C++ you could use a reference-to-a-pointer, but references and built-in pointers, are not thread safe. For example, if you have a reference to an object created on the heap, and then another thread decides to go ahead and delete that object, the next time you try to use your reference, bad things will happen. So back to the question, how do you pass a Smart Pointer into a function that will change what the Smart Pointer points to? If you pass the Smart Pointer in by reference, the code will still be thread safe if you have at least one active pointer pointing to the same object and on the same thread. If you pass an actual Active Pointer into a function, by reference, then you'll be ok. If you have a reference to an Active Pointer, which you received from another thread, and you pass that reference into a function, then your code would not be considered thread safe. Just remember that references can be thread safe if used properly, but it's best to pass Smart Pointers around by value if you can.