Smart Pointer Tutorial

Smart pointers are a very helpful tool to avoid manual memory management and all the issues connected with it (memory leaks, unclear ownership semantics, double deletions, exception safety, ...). Basically, smart pointers are class templates that encapsulate a raw pointer and free memory in the destructor. This makes RAII possible, a fundamental idiom for resource management in modern C++.

C++ libraries contain various smart pointer implementations which mainly differ in the way ownership is managed. The most important ownership semantics are:

Design

With the C++98 standard library and TR1, there are three smart pointers available: auto_ptr, shared_ptr and weak_ptr (which is always used together with shared_ptr). For many use cases, this is not enough. Thor provides three additional smart pointer types that are ready-to-use for arbitrary types. The intention is to abstract from low-level techniques like manual memory management, overloads of The Big Three, and worries about pointer validity.

Thor's SmartPtr module comes with low requirements. It is header-only and completely independent of SFML, so you can directly use it in your project.

For every smart pointer in Thor, the pointee type needn't be complete at the time of declaration. This behavior is adapted from raw pointers.

class Pointee; // incomplete type

Pointee*                  rawPointer;
thor::ScopedPtr<Pointee>  smartPointer;

The type needs only be complete when you initialize the smart pointer with a valid pointer. The advantage of this technique are lower dependencies, especially in header files. When you have the following class definition, there is no need to include the definition of the type Pointee, so the client doesn't have to know it. As a result, the compilation is faster, and changes in the definition of Pointee require no recompilation. This helps you to implement idioms such as Pimpl.

class Pointee; // forward declaration

class MyClass
{
     thor::ScopedPtr<Pointee> ptr;
};

Furthermore, Thor's smart pointers do not provide implicit conversions from and to raw pointer types. While these conversions may seem convenient, they can lead to unwanted behavior in many situations.

Basic operations

All smart pointers support the overloaded operators * and -> for dereferencing as well as a conversion to a bool-like type to test for validity.

thor::ScopedPtr<MyClass> ptr;
if (ptr) 
     // ptr points to a valid object
if (!ptr)
     // ptr is empty (NULL)

The member function Reset() sets the pointer to a new object or to NULL, when no argument is passed.

ptr.Reset(new MyClass); // Destroys old object, assigns new object
ptr.Reset();            // Destroys old object, assigns NULL

Scoped pointers

The class template thor::ScopedPtr is a simple smart pointer that invokes the destructor and delete operator of an object as soon as it goes out of scope. This smart pointer cannot be copied or assigned.

You can use it for the RAII idiom:

int main()
{
     thor::ScopedPtr<MyClass> ptr(new MyClass);
     ptr->DoSomething();
} // End of block: delete operator is called

Movable pointers

thor::MovedPtr can be used to transfer ownership from a smart pointer to another. A use case is a factory function that creates an object which is passed to the caller:

thor::MovedPtr<MyClass> CreateObject()
{
     thor::MovedPtr<MyClass> ptr(new MyClass);
     ptr->DoSomething();

     return ptr;
}

Smart pointers with implicit move semantics are useful in many situations, but you have to be careful because they don't behave as expected when it comes to copies and assignments. The copy constructor and copy assignment operator of thor::MovedPtr modify the origin:

thor::MovedPtr<MyClass> first(new MyClass);
thor::MovedPtr<MyClass> second = first;
// first is now empty, object moved to second

Copyable pointers

This chapter treats smart pointers with deep copy semantics. thor::CopiedPtr provides three different policies that specify how a copy is performed. You can also use your own policy to customize the behavior. The copy policy corresponds to the second template parameter of thor::CopiedPtr.

The simplest policy is thor::StaticCopy, it invokes the copy constructor of the pointee object:

thor::CopiedPtr<MyClass, thor::StaticCopy> first(new MyClass);
thor::CopiedPtr<MyClass, thor::StaticCopy> second = first;
// Object in first is copied to second, *first == *second

Things become more interesting when dynamic polymorphism is involved. Let's assume we have the following class hierarchy (we abandon const-correctness for simplicity):

class Base
{
    public:
        virtual ~Base() {}
        virtual Base* Clone() = 0;
        virtual int Number() = 0;
};

class Derived1 : public Base
{
     public:
         virtual Derived1* Clone() { return new Derived1(*this); }
         virtual int Number() { return 1; }
};

class Derived2 : public Base
{
     public:
         virtual Derived2* Clone() { return new Derived2(*this); }
         virtual int Number() { return 2; }
};

The virtual function Clone() returns an identical object of the derived type. Now we declare two base smart pointers with the thor::VirtualClone policy to make use of the Clone() function for deep copying.

thor::CopiedPtr<Base, thor::VirtualClone> p, q;
p.Reset(new Derived1);  // p->Number() == 1
q.Reset(new Derived2);  // q->Number() == 2

q = p; // Perform deep copy, destroy Derived2 object
// p->Number() == 1 and q->Number() == 1

The smart pointer objects have usual value semantics: We can copy and assign them, and internally the right derived object is chosen. The deep copy is safe, no object slicing occurs.

But defining this Clone() function in every class in inconvenient. Forgetting it may lead to object slicing if the function has already been defined in a base class. Unfortunately, we have to define it, or how should the pointer know in which way the object is copied?

What if I told you that we actually don't? Indeed, no Clone() function is required to perform deep copies across a polymorphic class hierarchy. And don't be afraid: We don't need any dynamic_cast, typeid, switch or if-else-cascade either. There is a very elegant solution encapsulated in thor::CopiedPtr combined with the thor::DynamicCopy policy.

After removing all the Clone() methods from the three classes, we can write the following, just as above:

thor::CopiedPtr<Base, thor::DynamicCopy> p, q;
p.Reset(new Derived1);  // p->Number() == 1
q.Reset(new Derived2);  // q->Number() == 2

q = p; // Perform deep copy, destroy Derived2 object
// p->Number() == 1 and q->Number() == 1

It does work. In case you wonder why: The constructor and Reset() are function templates that recognize the actual type of the new-expression. By knowing the dynamic type, it is possible to invoke the corresponding copy constructor. On the other side, you can't do something like this:

thor::CopiedPtr<Base, thor::DynamicCopy> p, q;
Base* raw = new Derived1;
p.Reset(raw);
q = p;

The pointer passed to Reset() is of type Base* and not Derived1*. Here, the thor::VirtualClone policy would work again.

Interaction between CopiedPtr and MovedPtr [advanced]

Sometimes we need to take ownership of a CopiedPtr without copying its content. Basically, the Release() function can be invoked to take ownership of a pointer:

// Transfer ownership from p to q
thor::CopiedPtr<MyClass, thor::StaticCopy> p(new MyClass);
thor::CopiedPtr<MyClass, thor::StaticCopy> q(p.Release());

But you need to make sure that the released pointer is immediately assigned to another smart pointer, or you have a memory leak. Anyway, this way becomes problematic as soon as you use the DynamicCopy policy with polymorphic objects, because Release()only returns a pointer to the base class. So you lose type information, which is essential for DynamicCopy in order to function correctly.

There is a much simpler and safer way, namely the function thor::Move():

thor::CopiedPtr<Base, thor::DynamicCopy> p(new Derived1);
thor::CopiedPtr<Base, thor::DynamicCopy> q = thor::Move(p);
// p is now empty, q contains the Derived1 object

The Move() function returns a MovedPtr with the same template arguments as the original CopiedPtr. Type information is retained. You can implicitly convert a MovedPtr object to a CopiedPtr object:

thor::MovedPtr<Base, thor::DynamicCopy> p(new Derived1);
thor::CopiedPtr<Base, thor::DynamicCopy> q = p;
// p is now empty

On the other side, you may want to copy the contents of a MovedPtr to another MovedPtr without transferring ownership, here you can use thor::Copy():

thor::MovedPtr<Base, thor::DynamicCopy> p(new Derived1);
thor::MovedPtr<Base, thor::DynamicCopy> q = thor::Copy(p);
thor::CopiedPtr<Base, thor::DynamicCopy> r = thor::Copy(p);
// p still contains the original object, q and r hold copies

To sum up, the following conversions are possible:

Conversion Transfer object Copy object
thor::MovedPtrthor::MovedPtr implicitly thor::Copy()
thor::MovedPtrthor::CopiedPtr implicitly thor::Copy()
thor::CopiedPtrthor::MovedPtr thor::Move() thor::Copy()
thor::CopiedPtrthor::CopiedPtr thor::Move() implictly