Getting a unique_ptr for a class that inherits enable_shared_from_this

♀尐吖头ヾ 提交于 2019-11-30 23:00:22

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;
}

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

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
}

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.

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>.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!