Tobias Hermann Tobias Hermann - 4 years ago 97
C++ Question

shared_ptr that cannot be null?

Using a

std::shared_ptr
expresses shared ownership and optionality (with its possibility to be null).

I find myself in situations where I want to express shared ownership only in my code, and no optionality. When using a
shared_ptr
as a function parameter I have to let the function check that it is not null to be consistent/safe.

Passing a reference instead of course is an option in many cases, but I sometimes would also like to transfer the ownership, as it is possible with a
shared_ptr
.

Is there a class to replace
shared_ptr
without the possibility to be null, some convention to handle this problem, or does my question not make much sense?

Answer Source

You could write a wrapper around std::shared_ptr that only allows creation from non-null:

#include <memory>
#include <cassert>

template <typename T>
class shared_reference
{
    std::shared_ptr<T> m_ptr;
    shared_reference(T* value) :m_ptr(value) { assert(value != nullptr);  }

public:
    shared_reference(const shared_reference&) = default;
    shared_reference(shared_reference&&) = default;
    ~shared_reference() = default;

    T* operator->() { return m_ptr.get(); }
    const T* operator->() const { return m_ptr.get(); }

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

    template <typename XT, typename...XTypes>
    friend shared_reference<XT> make_shared_reference(XTypes&&...args);

};


template <typename T, typename...Types>
shared_reference<T> make_shared_reference(Types&&...args)
{
    return shared_reference<T>(new T(std::forward<Types>(args)...));
}

Please note that operator= is missing yet. You should definitely add it.

You can use it like this:

#include <iostream>


using std::cout;
using std::endl;

struct test
{
    int m_x;

    test(int x)         :m_x(x)                 { cout << "test("<<m_x<<")" << endl; }
    test(const test& t) :m_x(t.m_x)             { cout << "test(const test& " << m_x << ")" << endl; }
    test(test&& t)      :m_x(std::move(t.m_x))  { cout << "test(test&& " << m_x << ")" << endl; }

    test& operator=(int x)          { m_x = x;                  cout << "test::operator=(" << m_x << ")" << endl; return *this;}
    test& operator=(const test& t)  { m_x = t.m_x;              cout << "test::operator=(const test& " << m_x << ")" << endl; return *this;}
    test& operator=(test&& t)       { m_x = std::move(t.m_x);   cout << "test::operator=(test&& " << m_x << ")" << endl; return *this;}

    ~test()             { cout << "~test(" << m_x << ")" << endl; }
};

#include <string>

int main() {

    {
        auto ref = make_shared_reference<test>(1);
        auto ref2 = ref;

        *ref2 = test(5);
    }
    {
        test o(2);
        auto ref = make_shared_reference<test>(std::move(o));
    }

    //Invalid case
    //{
    //  test& a = *(test*)nullptr;
    //  auto ref = make_shared_reference<test>(a);
    //}
}

Output:

test(1)
test(5)
test::operator=(test&& 5)
~test(5)
~test(5)
test(2)
test(test&& 2)
~test(2)
~test(2)

Example on Coliru

I hope I didn't forget anything that might result in undefined behaviour.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download