std::shared_ptr Wrapper

Home --> Programming Projects --> FPS Game Code --> Memory Code --> std::shared_ptr Wrapper

The Goal

C++ std::shared_ptr pointers are great. When we use the std::shared_ptr pointers to point to an object, the object will be deleted precisely when the last std::shared_ptr to the object is deleted.

The purpose of this page is to create a wrapper for this pointer that prints warning messages to try to get the caller to track the reference count.

For example, our wrapper class has a member function called Delete. This function first makes sure the reference count is 1 (otherwise it prints a warning message). It then sets the std::shared_ptr member to null.

The reason we print warnings instead of printing error messages and then exiting the program is because the reference count is only an approximation (because of multithreading).

An opposing point of view is the following: "Why care about when the destructor of the object gets called? It gets called when it gets called." However our wrapper class is useful to try to make sure that the destructor is called exactly when we want it to.

Data Members of the Class

The class has a the following data member:
std::shared_ptr m_ptr;
It also has the following data member:
std::string m_name;
That is, we give every pointer a string name which is printed in warning messages.

The Member Functions of the Class

Here are all the main member functions of our pointer class: Outside the class there is a helper function to create a null wrapper class pointer.

The Code

//File: SharedPtrWrapper.h

#ifndef SharedPtrWrapper_h_
#define SharedPtrWrapper_h_

#include <memory> //For "std::shared_ptr".
#include <string>
#include <iostream>
using std::string;
using std::cout;

//-----------------------------------------------
//
//             THREAD SAFE POINTER
//
//-----------------------------------------------

//When constructed using the default constructor,
//it is a null pointer.

template <class T>
class SharedPtrWrapper {
private:
    std::shared_ptr<T> m_ptr;

    //For debugging.
    string m_name;

    void DerefError1() const {
        cout
            << "*** Error: Thread Safe Pointer Error:\n"
            << "           Attempt to use -> on null SharedPtrWrapper\n";
        cout
            << "           Pointer name: " << m_name << "\n";
        exit(0);
    }

    void DerefError2() const {        
        cout
            << "*** Error: Thread Safe Pointer Error:\n"
            << "           Attempt to dereference null SharedPtrWrapper\n";
        string name = "\"" + m_name + "\"";
        cout
            << "           Pointer name: " << m_name << "\n";
        exit(0);
    }

public:
    SharedPtrWrapper() {
        m_ptr = nullptr;
    }
    
    SharedPtrWrapper(const SharedPtrWrapper<T> & b) {
        this->m_ptr = b.m_ptr;
        this->m_name = b.m_name;
    }

    ~SharedPtrWrapper() {
        if( this->GetRefCount() == 1 ) {
            cout << "*** Warning: Destructing a Thread Safe Pointer\n"
                <<  "    with a ref_count of 1.  This could be a \"leak\".\n"
                <<  "    Note that the ref_count is just an approximation.\n"
                <<  "    name = " << m_name << "\n";
        }
    }

    operator bool() const {
        return (bool)m_ptr;
    }

    //"Assert non-null".
    //Will exit if the raw ptr is null.
    void ANN() const {
        if( m_ptr ) return;
        cout << "*** Error: \"assert non-null\" "
            << "failed in thread safe pointer.\n"
            << "Pointer name = \"" << m_name << "\"\n";
        exit(0);
    }

    //In general, we want to avoid this.
    //Normally, all SharedPtrWrappers should be set to null
    //except the last one, which is deleted.
    //This is a backup method, where we can simply drop
    //each SharedPtrWrapper and there will not be a warning.
    void Drop() {
        m_ptr = nullptr;
    }

    //Could first check if ref count is > 1,
    //and otherwise say there is a "leak".
    void SetNull() {
        if( this->GetRefCount() == 1 ) {
            cout << "*** Warning: Setting a SharedPtrWrapper to null\n"
                <<  "    where the pointer has a ref_count of 1.\n"
                <<  "    This could be a \"leak\".\n"
                <<  "    Use Delete instead of SetNull on the last SharedPtrWrapper to an object.\n"
                <<  "    Note that the ref_count is just an approximation.\n"
                <<  "    name = " << m_name << "\n";
        }
        m_ptr = nullptr;
    }

    T * Raw() const {
        return m_ptr.get();
    }

    //Only an approximation.
    int GetRefCount() const {
        if( m_ptr ) {
            return m_ptr.use_count();
        } else {
            return 0;
        }
    }

    SharedPtrWrapper<T> & operator=(const SharedPtrWrapper<T> & b) {
        m_ptr = b.m_ptr;
        m_name = b.m_name;
        return *this;
    }
    
    T *operator->() const {
        if( !m_ptr ) {
            DerefError1();
        }
        return m_ptr.get();
    }

    T &operator*() const {
        if( !m_ptr ) {
            DerefError2();
        }
        return *(m_ptr.get());
    }

    bool IsNull() {
        return !(bool)m_ptr;
    }

    void SetNew(T * ptr) {
        if( m_ptr ) {
            cout << "*** Error: SharedPtrWrapper error:\n"
                <<  "    m_ptr is non null in SetNew\n"
                <<  "    old_name = " << m_name << "\n";
        }
        this->m_ptr = std::shared_ptr<T>(ptr);
    }

    void SetNew(
        T * ptr,
        const string & name)
    {
        if( m_ptr ) {
            cout << "*** Error: SharedPtrWrapper error:\n"
                <<  "    m_ptr is non null in SetNew\n"
                <<  "    old_name = " << m_name << "\n"
                <<  "    new_name = " << name << "\n";
        }
        this->m_ptr = std::shared_ptr<T>(ptr);
        this->m_name = name;
    }

    //If it had a name but now it is null,
    //this is the last name.
    string GetName() const {
        return this->m_name;
    }

    void SetName(const string & name) {
        this->m_name = name;
    }

    //Does not actually delete, just sets m_ptr to null.
    void Delete() {
        if( !m_ptr ) {
            cout << "*** Error: trying to delete a null SharedPtrWrapper.\n";
            return;
        }
        int ref_count = this->GetRefCount();

        //Will actually delete if m_ptr is the
        //only shared_ptr to the object.
        m_ptr = nullptr;

        if( ref_count > 1 ) {
            cout << "*** Warning: SharedPtrWrapper has ref_count = "
                << ref_count << " > 1\n"
                <<  "    just before delete.\n"
                <<  "    Note that the ref_count is just an approximation.\n"
                <<  "    name = " << m_name << "\n";
        }
    }

    void DeleteMaybe() {
        if( m_ptr ) this->Delete();
    }
};

template <class T>
SharedPtrWrapper<T> NULL_SHARED_PTR_WRAPPER() {
    SharedPtrWrapper<T> temp;
    return temp;
}

#endif

Example 1

void SharedPtrWrapperTest1() {
    SharedPtrWrapper<int> x;
    x.SetNew(
        new int,
        "my first int");
    SharedPtrWrapper<int> y;
    y = x;
    // y.DeleteMaybe(); //This would be a warning.
    // y.Delete();      //This would also be a warning.
    //Ref count is 2.
    y.SetNull();        //This is ok.
    //Ref count is 1.
    x.Delete();         //This is ok.
    //Ref count is 0.
}

Example 2

    SharedPtrWrapper<double> x;
    x.SetNew(
        new double,
        "my first double");
    SharedPtrWrapper<double> y;
    y = x;
    //Ref count is 2.
    y.Drop(); //This is ok.
    //Ref count is 1.
    x.Drop(); //This is ok.
    //Ref count is 0.