Amir Kirsh
Amir Kirsh

Reputation: 13770

Getting a unique_ptr for a class that inherits enable_shared_from_this

Usually I prefer returning unique_ptr from Factories. Recently I came to the problem of returning a unique_ptr for a class that inherits enable_shared_from_this. Users of this class may accidentally cause a call to shared_from_this(), though it is not owned by any shared_ptr, which results with a std::bad_weak_ptr exception (or undefined behavior until C++17, which is usually implemented as an exception).

A simple version of the code:

class Foo: public enable_shared_from_this<Foo> {
    string name;
    Foo(const string& _name) : name(_name) {}
public:
    static unique_ptr<Foo> create(const string& name) {
        return std::unique_ptr<Foo>(new Foo(name));
    }
    shared_ptr<Foo> get_shared() {return shared_from_this();}
    void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}
    virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};

int main() {
    // ok behavior
    auto pb1 = Foo::create("pb1");
    pb1->doIt();
    shared_ptr<Foo> pb2 = shared_ptr<Foo>(std::move(pb1));
    shared_ptr<Foo> pb3 = pb2->get_shared();
    pb3->doIt();

    // bad behavior
    auto pb4 = Foo::create("pb4");
    pb4->doIt();
    shared_ptr<Foo> pb5 = pb4->get_shared(); // exception
    pb5->doIt();    
}

A possible solution is to change the factory method to return shared_ptr but this is not what I'm looking for, as in many cases there is actually no need for sharing and this will make things less efficient.

The question is how to achieve all of the following:

  1. allow the factory to return unique_ptr
  2. allow unique_ptr of this class to become shared
  3. allow shared_ptr of this class to get shared copies (via shared_from_this())
  4. avoid failure when unique_ptr of this class tries to get shared from this (calling get_shared in above example)

Items 1 to 3 are fulfilled with the code above, the problem is with item 4.

Upvotes: 6

Views: 3155

Answers (7)

tme
tme

Reputation: 1

An old thread already, but I stumbled to ponder a question if its always quaranteed that weak_from_this().expired()==true or weak_from_this().use_count() == 0 for an object not managed by shared_ptr - including unique_ptr. If yes, then simple answer to original question could be to realize non-throwing version of shared_from_this() via such checks for objects returned from factory.

Upvotes: 0

Caleth
Caleth

Reputation: 62884

Deriving from enable_shared_from_this is a promise that your instances are owned by a (group of) shared_ptr. Don't lie about this, only ever create shared_ptr<Foo> and never hand out unique_ptr<Foo>.

It's not worth the future pain when you have to disentangle the "safe" uses of unique_ptr<Foo> from the case where a Foo & deep in some logic that wants to call shared_from_this but sometimes is actually a unique_ptr<Foo>.

Upvotes: 2

jonspaceharper
jonspaceharper

Reputation: 4367

Use multiple static factory functions and conversion functions. To address your comments, I've added get_shared to support copying a shared pointer. This compiles and is available here: http://ideone.com/UqIi3k

#include <iostream>
#include <memory>

class Foo
{
    std::string name;
    Foo(const std::string& _name) : name(_name) {}
public:
    void doIt() const { std::cout << "Foo::doIt() <" << name << '>' << std::endl;}
    virtual ~Foo() { std::cout << "~Foo() <" << name << '>' << std::endl;}

    static std::unique_ptr<Foo> create_unique(const std::string & _name) {
        return std::unique_ptr<Foo>(new Foo(_name));
    }
    static std::shared_ptr<Foo> create_shared(const std::string & _name) { 
        return std::shared_ptr<Foo>(new Foo(_name));
    }

    static std::shared_ptr<Foo> to_shared(std::unique_ptr<Foo> &&ptr ) { 
         return std::shared_ptr<Foo>(std::move(ptr)); 
    }
    static std::shared_ptr<Foo> get_shared(const std::shared_ptr<Foo> &ptr) {
         return std::shared_ptr<Foo>(std::move(ptr));
    }
};

int main() {
    // ok behavior
    auto pb1 = Foo::create_unique("pb1");
    pb1->doIt();
    std::shared_ptr<Foo> pb2 = Foo::get_shared(std::move(pb1));
    //note the change below
    std::shared_ptr<Foo> pb3 = Foo::get_shared(pb2);
    pb3->doIt();

    // also OK behavior
    auto pb4 = Foo::create_unique("pb4");
    pb4->doIt();
    std::shared_ptr<Foo> pb5 = Foo::to_shared(std::move(pb4)); // no exception now
    pb5->doIt();

    std::shared_ptr<Foo> pb6 = Foo::create_shared("pb6");
    pb6->doIt();
    std::shared_ptr<Foo> pb7 = std::shared_ptr<Foo>(pb5);
    pb7->doIt();
    return 0;
}

Upvotes: 1

utnapistim
utnapistim

Reputation: 27365

That's exactly what I was looking for, making sure that clients may reach a point that sharing is required and it will work transparently without really caring if they are with a shared Pet or with a unique Pet (i.e. making the interface easy to use correctly etc.).

It sounds like the x-y problem to me.

To "make sure that clients can share if required", turn this into a separate tool and put this in your toolset (edit: but it still feels like you have the x-y problem):

namespace tools
{

    /// @brief hides the details of sharing a unique pointer
    ///        behind a controlled point of access
    ///
    /// to make sure that clients can share if required, use this as a
    /// return type
    template<typename T>
    class pointer final
    {
    public:

        // @note: implicit on purpose (to enable construction on return,
        //        in the client code below)
        pointer(std::unique_ptr<T> value);

        // @note: implicit on purpose (to enable construction on return,
        //        in the client code below)
        pointer(std::shared_ptr<T> value);

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

        /// @note copy&swap
        pointer& operator=(pointer p)
        {
            using std::swap;
            swap(value1, p.value1);
            swap(value2, p.value2);
            return *this;
        }

        // example copy
        pointer(const pointer<T>& value)
        : value1{}, value2{ value.share() }
        {
        }

        // example move
        pointer(pointer<T>&& tmp)
        : value1{ std::move(tmp.value1) }, value2{ std::move(tmp.value2) }
        {
        }

        /// @note conceptually const, because it doesn't change the address
        ///       it points to
        ///
        /// @post internal implementation is shared
        std::shared_ptr<T> share() const
        {
            if(value2.get())
                return value2;

            value2.reset(value1.release());
                return value2;
        }

        T* const get() const
        {
             if(auto p = value1.get())
                 return p;
             return value2;
        }

    private:
        mutable std::unique_ptr<T> value1;
        mutable std::shared_ptr<T> value2;
    };
}

Your client code then becomes:

class Foo
{
    string name;
    Foo(const string& _name) : name(_name) {}
public:

    using pointer = tools::pointer<Foo>;

    static pointer make_unique(const string& name)
    {
        return std::make_unique<Foo>(name);
    }

    void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}

    virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};

int main() {
    // ok behavior
    auto pb1 = Foo::make_unique("pb1");
    pb1->doIt(); // call through unique pointer
    auto pb2 = pb1.share(); // get copy of shared pointer
    auto pb3 = pb1; // get copy of shared pointer

    auto pb4 = std::move(pb1); // move shared pointer
}

Upvotes: 1

Raghavendar Reddy
Raghavendar Reddy

Reputation: 99

May be we can template check of the calling variable ::

class Foo : public enable_shared_from_this<Foo> {
    string name;
    Foo(const string& _name) : name(_name) {}
public:
    static unique_ptr<Foo> create(const string& name) {
        return std::unique_ptr<Foo>(new Foo(name));
    }
    template <typename T>
        shared_ptr<Foo> get_shared() { return shared_ptr<Foo>(); }
    template <>
    shared_ptr<Foo> get_shared<unique_ptr<Foo>>() { return shared_ptr<Foo>(); }

    template <>
    shared_ptr<Foo> get_shared<shared_ptr<Foo>>() { return shared_from_this(); }

    void doIt()const { cout << "Foo::doIt() <" << name << '>' << endl; }
    virtual ~Foo() { cout << "~Foo() <" << name << '>' << endl; }
};

int main()
{
    // ok behavior
    auto pb1 = Foo::create("pb1");
    pb1->doIt();
    shared_ptr<Foo> pb2{ std::move(pb1) };
    shared_ptr<Foo> pb3 = pb2->get_shared<decltype(pb2)>();
    pb3->doIt();

    // bad behavior
    auto pb4 = Foo::create("pb4");
    pb4->doIt();
        shared_ptr<Foo> pb5 = pb4->get_shared<decltype(pb4)>(); // exception
        if (pb5 != nullptr)
            pb5->doIt();

    return 0;
}

I am not sure if that is exactly what you want, but might solve the point 4 you have mentioned.

Upvotes: -1

Richard Hodges
Richard Hodges

Reputation: 69892

As you will have found, once an object is managed by a shared_ptr, there is no (safe) way to unshare it. This is by design, since once the lifetime of the object is shared, no one owner can guarantee that it will ever be the sole owner again (except during the execution of the deleter - I'll leave you to reason about whether that's useful to you).

However, the standard library does allow you to specialise any std class template provided it's specialised for a user defined class.

So what you can do is this:

namespace std {
  template<class Deleter>
  struct unique_ptr<Foo, Deleter> {
    static_assert(!std::is_same<Deleter, Deleter>::value, "sorry, not allowed");
    // or
    static_assert(!sizeof(Deleter), "sorry, not allowed");
    // (thanks to Jarod42 && md5i for cleaning up after me)
  };
}

And now no unique_ptr can ever own your object (which is clearly designed to be owned only by a shared_ptr).

This is enforced at compile time.

Upvotes: 0

Amir Kirsh
Amir Kirsh

Reputation: 13770

The problem with the member function get_shared in the question is that it allows calls by both unique_ptr and shared_ptr with a difficult to distinguish between the two, thus unique_ptr is allowed to call this method and fails.

Moving the get_shared to be a static method which gets the pointer to share from, allows the distinguishing between unique and share which fixes this problem:

class Foo: public enable_shared_from_this<Foo> {
    string name;
    Foo(const string& _name) : name(_name) {}
public:
    static unique_ptr<Foo> create(const string& name) {
        return std::unique_ptr<Foo>(new Foo(name));
    }
    static shared_ptr<Foo> get_shared(unique_ptr<Foo>&& unique_p) {
        return shared_ptr<Foo>(std::move(unique_p));
    }
    static shared_ptr<Foo> get_shared(const shared_ptr<Foo>& shared_p) {
        return shared_p->shared_from_this();
    }
    void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}
    virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};

int main() {
    // ok behavior - almost as before
    auto pb1 = Foo::create("pb1");
    pb1->doIt();
    shared_ptr<Foo> pb2 = shared_ptr<Foo>(std::move(pb1));
    shared_ptr<Foo> pb3 = Foo::get_shared(pb2);
    pb3->doIt();

    // ok behavior!
    auto pb4 = Foo::create("pb4");
    pb4->doIt();
    shared_ptr<Foo> pb5 = Foo::get_shared(std::move(pb4));
    pb5->doIt();    
}

Code: http://coliru.stacked-crooked.com/a/7fd0d462ed486c44

Upvotes: 2

Related Questions