获取继承enable_shared_from_this的类的unique_ptr

时间:2016-06-27 15:39:08

标签: c++ c++11 shared-ptr smart-pointers unique-ptr

通常我更喜欢从工厂返回unique_ptr。 最近我遇到了为继承unique_ptr的类返回enable_shared_from_this的问题。此类的用户可能会意外地调用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();    
}

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

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

  1. 允许工厂返回unique_ptr
  2. 允许此类的unique_ptr成为共享
  3. 允许此类的shared_ptr获取共享副本(通过shared_from_this()
  4. 避免在此类的unique_ptr尝试与此共享时失败(在上面的示例中调用get_shared
  5. 上述代码符合第1至3项,问题在于第4项

7 个答案:

答案 0 :(得分:2)

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

get_shared移动为获取指向共享的指针的静态方法,允许区分unique和share来解决此问题:

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

答案 1 :(得分:1)

  

这正是我所寻找的,确保客户可以达到需要分享的程度,如果他们使用共享宠物或独特的宠物(即制作),它将在没有真正关怀的情况下透明地工作界面易于正确使用等。)。

这听起来像x-y问题。

要确保客户可以在需要时共享&#34;,将其转换为单独的工具并将其放入您的工具集中(编辑>但仍感觉您拥有xy问题):

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
}

答案 2 :(得分:1)

使用多个静态工厂功能和转换功能。为了解决您的评论,我添加了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;
}

答案 3 :(得分:0)

正如您将发现的那样,一旦对象由User.joins(:requests).includes(:requests).order('CASE WHEN requests.created_at IS NULL THEN 1 ELSE 0 END asc, requests.created_at desc') 管理,就没有(安全)方法来取消共享它。这是设计的,因为一旦对象的生命周期被共享,没有任何一个所有者可以保证它将再次成为唯一的所有者(除了在执行删除器期间 - 我将让你去理解这是否对你)。

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

所以你能做的就是:

shared_ptr

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

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

答案 4 :(得分:0)

源自enable_shared_from_this的承诺是您的实例由shared_ptr(的一组)拥有。 不要撒谎,只能创建shared_ptr<Foo>,并且永远分发unique_ptr<Foo>

如果您不得不将unique_ptr<Foo>的“安全”用法与某些需要调用Foo &的逻辑中的shared_from_this分开,但< em>有时实际上是一个unique_ptr<Foo>

答案 5 :(得分:0)

已经有一个旧线程,但我偶然想出一个问题,如果它总是隔离weak_from_this().expired()==trueweak_from_this().use_count() == 0而不是由shared_ptr管理的对象(包括unique_ptr)。如果是,那么对原始问题的简单答案可能是通过对工厂返回的对象进行此类检查来实现shared_from_this()的非抛出版本。

答案 6 :(得分:-1)

我们可以模板检查调用变量::

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点。