Smart Pointers

Top1 Exclusive ownership (unique_ptr)

Basically unique_ptr encapsulates a delete of an embedded raw pointer within the smart pointer's destructor.
Typical code snippets
#include <memory>

class MyResource;

// specific typedef for smart pointer
typedef std::unique_ptr<MyResource> MyResourceUp;

...

// client code using resource through unique_ptr 
{
    // Aquire resource (e.g. create object) and store within unique_ptr
    MyResourceUp upMyResource (new MyResource ("some resource"));

    // Use resource through unique_ptr
    upMyResource->DoSomething();

} // upMyResource leaves scope -> MyResource is deleted
  // this works even in case of exceptions!

Principles
  • when the unique pointer gets deleted or when a new value is assigned the currently embedded resource is deleted
  • reset(): delete the embedded resource
  • reset(pNewResource): delete the embedded resource and store pointer to new resource
  • release: returns the embedded raw pointer and detaches unique_ptr from the resource. After this call the resource has to be deleted with other means.
  • in contrast to deprecated auto_ptr copy semantics are disabled (copy constructors are private).
    The former problem with auto_ptr was: when they were stored within containers some algorithms may use copy semantics to assign a value to a temporary variable and such the stored values may get lost:
    // assume myVector contains auto_ptr instances holding some resources
    // some std algorithms may contain code of the following type:
    
    std::auto_ptr temp = myVector[i]; // unwanted transfer of ownership
    
    // Danger: this instruction moves the ownership from the vector element to
    // the temporary variable, when the temp variable gets out of scope the
    // corresponding resource is deleted.
    

    unique_ptr does no longer allow this kind of error:

    std::unique_ptr temp = myVector[i]; // compile error
    
    // explicit usage of move semantics is needed:
    std::unique_ptr temp = std::move(myVector[i]);
    

    Now you can safely use unique_ptr within containers and call any algorthms. If the container or algorithm uses (unwanted) copy semantics you will get a compile time error. If the container and algorithm uses move semantics all will work as desired.

  • implicit transfer of ownership for rvalues, using explicit move semantics when working with lvalues
    class MyResource;
    typedef std::unique_ptr<MyResource> MyResourceUp;
    
    MyResourceUp CreateResource ()
    {
        MyResourceUp up (new MyResource("Created Resource"));
        return up;
    
    } 
    
    {
        MyResourceUp upMyResource;
    
        // simple assigning of new values,
        // implicitly using move semantics with transfer of ownership:
    
        // function return value is a rvalue
        upMyResource = CreateResource(); 
    
        // temporary object is a rvalue 
        upMyResource = MyResourceUp(new MyResource ("Resource B"));
    
    
        // explicit transfer of ownership needed
        MyResourceUp upOther;
    
        upOther = upMyResource; // will not compile,
                                // upMyResource is an lvalue!
    
        // explicitly confirm move semantics
        upOther = std::move(upMyResource); // OK
    }
    
  • When storing pointers to array within unique_ptr explicitly use array notation:
    std::unique_ptr<int[]> up(new int[10]);
    
    With this definition the correct deleter "delete[]" will be called. Forgetting "[]" within definition of unique_ptr will compile without errors, but then destruction will no longer work correctly!

Top2 Shared ownership (shared_ptr)

shared_ptr is a reference counted, non-intrusive smart pointer for sharing the access to some resource with several clients.
Typical code snippets
#include <memory>

// class representing the resource
class MyResource
{
public:
    void DoSomething();
};

// specific typedef for smart pointer
typedef std::shared_ptr<MyResource> MyResourceSp;

...

// client code using resource through shared_ptr 
{
    // Aquire resource (e.g. create object) and store within shared_ptr
    MyResourceSp spMyResource (new MyResource ("some resource"));

    // Use resource through shared_ptr
    spMyResource->DoSomething();

    // Pass pointer to resource to some other component
    someOtherComponent.Transfer (spMyResource);
    // Remark: the other component may or may not store the
    // pointer for later use!

} // spMyResource leaves scope -> reference count is decremented
  // only when count reaches 0 resources are freed/deleted

Principles
  • when the last shared_ptr to a resource is reset or deleted the resource is released.
  • as default behaviour releasing of a resource means "delete"
    you can change this behaviour by specifying a specific deleter when assigning a new resource to a shared_ptr:
    // custom deleter writing info to std::out before deleting
    shared_ptr<MyResource> spMyResource (new MyResource,
        [](MyResource* pMyRespource)
        {
            std::cout << "deleting " << pMyRespource->GetSomeInfo() std::endl;
            delete pMyResource
        });
    
  • std::make_shared is a performant factory function both creating the specific resource and the internally used smart pointer control block within a single heap allocation:
    // instead of
    MyResourceSp spMyResource (new MyResource ("some resource"));
    
    // write
    MyResourceSp spMyResource = std::make_shared<MyResource>("some resource");
    
    the arguments passed to make_shared are identical to the arguments you have defined as constructor arguments for your resource
  • there is no possibility for detaching a resource (a raw pointer) when it has been transferred to a shared_ptr. The only way of resource release/deletion is through reset of all shared_ptr instances.
    Reason: other shared_ptr instances would not know about a local detach and may continue with watching reference counting. It would be unclear who is responsible for final resource deletion.
  • access to raw pointer via shared_ptr::get() is possible, but call may return null if the shared_ptr does not own a resource
  • important: before storing a raw pointer within a shared_ptr the programmer must be sure that deletion shall be managed by the shared_ptr,
    recommmendation to avoid such problems: always pass around as smart pointer type and not as raw pointer.
    Incorrect usage may lead to missing deletion or even program crash when multiple independent shared_ptr instances try to delete the same resource:
    // bad idea:
    MyResourceSp spMyResource(new MyResource);
    MyResource* pMyResource = spMyResource.get();
    MyResourceSp spMyResource2(pMyresource);
    
    // spMyResource and spMyResurce2 don't know from each other and
    // both will try to delete/release the resource
    

Top3 Shared access without ownership (weak_ptr)

weak_ptr is a smart pointer class which does only make sense when used together with one or more shared_ptr instances. The resource has to be owned by at least one client using a shared_ptr, while other clients can have access through weak_ptr instances. There is no guarantee that the resource still exists when a client tries to access the resource through its weak_ptr.

A weak_ptr can be used to solve the problem of cyclic references:
Classes A and B use and own each other through sharewd_ptr instances. Without specific programing techniques (e.g. shutdown protocol which leads to a release of all smart pointers) they will never be deleted.

Typical code snippets
// class representing the resource
class MyResource
{
public:
    void DoSomething();
};

// specific typedef for smart pointers
typedef std::shared_ptr<MyResource> MyResourceSp;
typedef std::weak_ptr<MyResource> MyResourceWp;

...
// Aquire resource (e.g. create object) and store within shared_ptr
MyResourceSp spMyResource (new MyResource ("some resource"));

// Store shared pointer to resource within weak pointer
// (real code may pass the pointer to some other component which wants to use
// the resource but does not want to keep the resource alive)
MyResourceWp wpMyResource(spMyResource);

...

// elsewhere in code
// use the resource through weak_ptr

{
    // create temporary shared_ptr and get ownership of resource
    // but be prepared: the resource may have been deleted!
    MyResourceSp spTmp = wpMyResource.lock();

    if (spTmp) // Check if resource still exists
    {
        spTmp->DoSomething();
    }
} // relase temporary ownership by deletion of temp shared_ptr

Principles
  • before accessing the resource the weak_ptr hast to be converted to a shared_ptr through weak_ptr::lock()
  • the returned shared pointer may be null, i.e. the resource may have been deleted
  • The programmer has actively to decide who owns the resource and who only stores a weak pointer. This lifecycle scheme is a part of the program design.

Top4 Behind the scenes of shared_ptr and weak_ptr

Top4.1 The control block

Situation: resource is accessed by multiple smart pointer instances:

Smart pointer

All instances of shared_ptr and weak_ptr which are connected to the same resource depend on the same "control block" instance. The control block (as part of the std lib code) is responsible for reference counting and resource deletion.

Situation: all shared_ptr instances have been deleted or reset:
Smart pointer

When all shared_ptr instances are deleted/reset the regular reference count within the control block is decremented to 0 and the resource is deleted. All existing weak_ptr instances are still connected to the control block (see picture).

The control block will be deleted when the weak reference count is decremented to 0. This will happen when the last weak_ptr instance is reset.

Top4.2 Complete code sample

Accessing resource through several smart pointers
class MyResource;
typedef std::shared_ptr<MyResource> MyResourceSp;
typedef std::weak_ptr<MyResource> MyResourceWp;

{
    // Aquire resource (e.g. create object) and store within shared_ptr
    MyResourceSp spMyResource (new MyResource ("some resource"));

    // Use resource through shared_ptr
    spMyResource->DoSomething();

    // Store pointer to resource within weak pointer
    // (real code may pass the pointer to some other component which wants to use
    // the resource but does not want to keep the resource alive)
    MyResourceWp wpMyResource(spMyResource);

    // Using resource through weak_ptr is only possible after converting to shared_ptr
    {
        Check("After initialization", spMyResource, wpMyResource);

        MyResourceSp spTmp = wpMyResource.lock();

        Check("Use count is incremented to 2 because a temporary shared_ptr "
	    "holds a reference to resource", spMyResource, wpMyResource);

        if (spTmp) // Check if shared_ptr points to something
	{
            spTmp->DoSomething();
        }

    } // temp shared_ptr goes out of scope, refcount is decremented

    Check("temp shared_ptr has gone out of scope", spMyResource, wpMyResource);

    spMyResource.reset();

    Check("shared_ptr has been reset", spMyResource, wpMyResource);

    // store new resource within spMyResource#
    // the original resource is released automatically
    spMyResource.reset(new MyResource("another resource"));

    // the weak pointer originally connected with spMyRespource stays invalid
    // because the original resource has been deleted
    // (technically the weak pointer stays connected with the control block
    // for the already deleted resource)
    Check("shared_ptr now stores new resource", spMyResource, wpMyResource);

}   // "another resource" gets released when spMyResource gets out of scope

Output to stdout

some resource: Constructor
some resource: doing something

Info: After initialization
shared_ptr owns some resource useCount=1
weak_ptr points to some resource useCount=1

Info: Use count is incremented to 2 because a temporary
    shared_ptr holds a reference to resource
shared_ptr owns some resource useCount=2
weak_ptr points to some resource useCount=2
some resource: doing something

Info: temp shared_ptr has gone out of scope
shared_ptr owns some resource useCount=1
weak_ptr points to some resource useCount=1
some resource: Destructor

Info: shared_ptr has been reset
shared_ptr does not own a resource
weak_ptr points to null
another resource: Constructor

Info: shared_ptr now stores new resource
shared_ptr owns another resource useCount=1
weak_ptr points to null
another resource: Destructor

Helper code

class MyResource
{
public:
    // Constructor
    MyResource (std::string const & in_name)
    : m_name (in_name)
    {
        std::cout << m_name.c_str() << ": Constructor" << std::endl;
    }

    // Destructor
    ~MyResource (void)
    {
        std::cout << m_name.c_str() << ": Destructor" << std::endl;
    }

    // Write name to stdout
    void DoSomething (void)
    {
        std::cout << m_name.c_str() << ": doing something" << std::endl;
    }

    std::string GetName (void)
    {return m_name;}

private:
    std::string m_name;
};

void CheckSharedPtr (MyResourceSp& in_spResource)
{
    if (!in_spResource)
    {
        std::cout << "shared_ptr does not own a resource" << std::endl;
    }
    else
    {
        std::cout << "shared_ptr owns " << in_spResource->GetName().c_str()
            << " useCount=" << in_spResource.use_count() << std::endl;
    }
}

void CheckWeakPtr (MyResourceWp& in_wpResource)
{
    long useCount = in_wpResource.use_count();
    MyResourceSp spTmp = in_wpResource.lock();
    if (!spTmp)
    {
        std::cout << "weak_ptr points to null" << std::endl;
    }
    else
    {
        std::cout << "weak_ptr points to " << spTmp->GetName().c_str()
            << " useCount=" << useCount << std::endl;
    }
}

void Check (std::string const & in_info,
    MyResourceSp& in_spResource,
    MyResourceWp& in_wpResource)
{
    std::cout << std::endl << "Info: " << in_info.c_str() << std::endl;
    CheckSharedPtr (in_spResource);
    CheckWeakPtr (in_wpResource);
}