为继承 enable_shared_from_this 的 class 获取 unique_ptr

Getting a unique_ptr for a class that inherits enable_shared_from_this

通常我更喜欢 returning unique_ptr 来自工厂。 最近我遇到了 return 为继承 enable_shared_from_this 的 class 设置 unique_ptr 的问题。 class 的用户可能会意外调用 shared_from_this(),尽管它不属于任何 shared_ptr,这会导致 std::bad_weak_ptr 异常(或未定义的行为,直到 C+ +17,通常作为异常实现)。

代码的简单版本:

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

一种可能的解决方案是将工厂方法更改为 return shared_ptr 但这不是我要找的,因为在许多情况下实际上不需要共享,这将降低效率。

问题是如何实现以下所有目标:

  1. 允许工厂returnunique_ptr
  2. 允许 class 中的 unique_ptr 成为共享
  3. 允许 class 的 shared_ptr 获得共享副本(通过 shared_from_this()
  4. 避免此 class 的 unique_ptr 尝试从此共享时失败(在上例中调用 get_shared

项目 1 到 3 已通过上述代码实现,问题出在项目 4

问题中成员函数 get_shared 的问题是它允许 unique_ptrshared_ptr 两者调用,很难区分两者,因此 unique_ptr 允许调用此方法但失败。

get_shared 移动为获取共享指针的静态方法,允许区分唯一和共享,这解决了这个问题:

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

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

您会发现,一旦某个对象由 shared_ptr 管理,就没有(安全的)方法来取消共享它。这是设计使然,因为一旦对象的生命周期被共享,任何所有者都不能保证它会再次成为唯一的所有者(删除器执行期间除外——我会让你去推理这是否对你)。

但是,标准库确实允许您专门化任何标准 class 模板,前提是它专门用于 用户定义的 class .

所以你可以做的是:

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

现在没有 unique_ptr 可以拥有您的对象(显然设计为仅由 shared_ptr 拥有)。

这是在编译时强制执行的。

也许我们可以对调用变量进行模板检查::

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

我不确定这是否正是您想要的,但可能会解决您提到的第 4 点。

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

对我来说这听起来像是 x-y 问题。

到"make sure that clients can share if required",把它变成一个单独的工具并把它放在你的工具集中(编辑:但感觉你仍然有 x-y 问题):

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

您的客户端代码将变为:

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
}

使用多个静态工厂函数和转换函数。为了解决您的意见,我添加了 get_shared 以支持复制共享指针。这编译并可在此处获得: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;
}

enable_shared_from_this 派生是一个承诺,即您的实例由 shared_ptr(一组)所有。 不要在这件事上撒谎,只创造shared_ptr<Foo>并且从不分发unique_ptr<Foo>

当你不得不将 unique_ptr<Foo> 的 "safe" 用法与 Foo & 深入某些想要调用 [=16] 的逻辑的情况分开时,不值得未来的痛苦=] 但 有时 实际上是 unique_ptr<Foo>.

已经是一个旧线程了,但我无意中思考了一个问题,如果它总是保证 weak_from_this().expired()==trueweak_from_this().use_count() == 0 不受 shared_ptr 管理的对象 - 包括 unique_ptr.如果是,那么对原始问题的简单回答可能是通过对从工厂返回的对象进行此类检查来实现 shared_from_this() 的非抛出版本。