【问题标题】:When should we use std::enable_shared_from_this我们什么时候应该使用 std::enable_shared_from_this
【发布时间】:2016-12-28 15:08:08
【问题描述】:

我只知道std::enable_shared_from_this 形成this link
但是看了下面的代码,不知道什么时候用。

try {
        Good not_so_good;
        std::shared_ptr<Good> gp1 = not_so_good.getptr();
    } catch(std::bad_weak_ptr& e) {
        // undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
        std::cout << e.what() << '\n';    
    }

上面的代码“不太好”,因为在调用getptr() 之前没有现有的shared_ptr。所以好的应该是:

std::shared_ptr<Good> gp1 = std::make_shared<Good>(); // having a shared_ptr at the beginning
std::shared_ptr<Good> gp2 = gp1->getptr();

但是,如果我已经有一个shared_ptr 对象,我为什么不简单地编写如下代码:std::shared_ptr&lt;Good&gt; gp2 = gp1;,这意味着我根本不需要std::enable_shared_from_this

在我看来,使用std::enable_shared_from_this 是为了确保多个shared_ptr 对象具有相同的控制块,这样我们就可以避免the double-delete problem。但是如果我必须在开始时提醒自己创建一个shared_ptr,为什么我不提醒自己使用shared_ptr 对象来创建一个新对象,而不是使用原始指针呢?

【问题讨论】:

  • void f(Good&amp; g); /* ... */ f(*gp1); /* ... */ void f(Good&amp; g) { /* now what? */ }
  • @milleniumbug 对不起,我不明白你想说什么。
  • f 无权访问shared_ptr,因为它需要一个引用,而不是共享指针。有时您可以进行更改,以便改为使用引用,但不适用于成员函数 - this 始终是原始指针。
  • @milleniumbug 我想你误解了我的问题。我的问题是:使用std::enable_shared_from_this 并不能让我们免于双重删除的风险。如果是这样,std::enable_shared_from_this 就没用了。查看示例:ideone.com/FlvIWw
  • 你错了。 std::enable_shared_from_this 不应该是防止滥用shared_ptr 界面的保护措施。有关它的用例,请参阅我的答案。

标签: c++ shared-ptr smart-pointers enable-shared-from-this


【解决方案1】:

std::enable_shared_from_this&lt;T&gt; 何时有用的提示在其名称中:当基于某些请求产生对象时,可能需要返回指向对象本身的指针。如果结果应该是std::shared_ptr&lt;T&gt;,则有必要从通常无法访问std::shared_ptr&lt;T&gt; 的成员函数中返回这样的指针。

std::enable_shared_from_this&lt;T&gt; 派生提供了一种获取std::shared_ptr&lt;T&gt; 的方法,只需一个T 类型的指针。但是,这样做会假定该对象已经通过std::shared_ptr&lt;T&gt; 进行管理,并且如果该对象被分配在堆栈上,则会造成混乱:

struct S: std::enable_shared_from_this<S> {
    std::shared_ptr<S> get_object() {
        return this->shared_from_this();
    };
}

int main() {
    std::shared_ptr<S> ptr1 = std::make_shared<S>();
    std::shared_ptr<S> ptr2 = ptr1->get_object();
    // ...
}

在现实场景中,可能会在某些情况下返回当前对象的std::shared_ptr&lt;T&gt;

【讨论】:

    【解决方案2】:

    有些用例不能像不透明指针一样使用模板std::shared_ptr&lt;T&gt;

    在这种情况下,这样做很有用:

    在 some_file.cpp 中

    struct A : std::enable_shared_from_this<A> {};
    
    extern "C" void f_c(A*);
    extern "C" void f_cpp(A* a) {
       std::shared_ptr<A> shared_a = a->shared_from_this();
       // work with operation requires shared_ptr
    }
    
    int main()
    {
        std::shared_ptr<A> a = std::make_shared<A>();
        f_c(a.get());
    }
    

    在 some_other.c 中

    struct A;
    void f_cpp(struct A* a);
    void f_c(struct A* a) {
        f_cpp(a);
    }
    

    【讨论】:

    • 我有点困惑,为什么要在 f_cpp 中使用 sharea_ptr?如果 f_cpp 需要,它应该接受 shared_ptr 而不是原始 pointef A*。
    【解决方案3】:

    假设我想表示一个计算树。我们将一个加法表示为从表达式派生的类,该类具有两个指向表达式的指针,因此可以递归地评估表达式。但是,我们需要在某个地方结束评估,所以让我们自己评估数字。

    class Number;
    
    class Expression : public std::enable_shared_from_this<Expression>
    {
    public:
        virtual std::shared_ptr<Number> evaluate() = 0;
        virtual ~Expression() {}
    };
    
    class Number : public Expression
    {
        int x;
    public:
        int value() const { return x; }
        std::shared_ptr<Number> evaluate() override
        {
            return std::static_pointer_cast<Number>(shared_from_this());
        }
        Number(int x) : x(x) {}
    };
    
    class Addition : public Expression
    {
        std::shared_ptr<Expression> left;
        std::shared_ptr<Expression> right;
    public:
        std::shared_ptr<Number> evaluate() override
        {
            int l = left->evaluate()->value();
            int r = right->evaluate()->value();
            return std::make_shared<Number>(l + r);
        }
        Addition(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right) :
            left(left),
            right(right)
        {
    
        }
    };
    

    Live on Coliru

    请注意,用return std::shared_ptr&lt;Number&gt;(this); 实现Number::evaluate() 的“明显”方式被破坏了,因为它会导致双重删除。

    【讨论】:

    • evaluate 的返回类型应该是std::shared_ptr&lt;Number&gt;,让你免于所有邪恶的铸造。
    • 您忘记指定NumberAddition 的基类。
    • @BenVoigt 是的,在这个示例代码中,只需这样做。我的程序有不同的类型,这就是我最初没有这样做的原因。 (我不得不使用std::shared_from_this() 的演员表也很遗憾,但我想这就是生活)
    • @Oktalist 已修复。还添加了一个指向 coliru 的链接。
    • 您必须将shared_ptr&lt;Expression&gt; 显式转换为shared_ptr&lt;Number&gt;,原因与使用原始指针时必须将Expression* 显式转换为Number* 相同。如果Expression::evaluate 的返回类型是shared_ptr&lt;Expression&gt;(这似乎是合理的),那么shared_ptr&lt;Number&gt; 将不符合协变返回类型,这很不幸。
    猜你喜欢
    • 2021-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 2011-07-04
    • 2017-09-13
    • 2021-12-29
    • 2011-07-17
    相关资源
    最近更新 更多