我们啥时候应该使用 std::enable_shared_from_this

Posted

技术标签:

【中文标题】我们啥时候应该使用 std::enable_shared_from_this【英文标题】: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 界面的保护措施。有关它的用例,请参阅我的答案。 【参考方案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; 将不符合协变返回类型,这很不幸。

以上是关于我们啥时候应该使用 std::enable_shared_from_this的主要内容,如果未能解决你的问题,请参考以下文章

我们啥时候应该使用 PreparedStatement 而不是 Statement?

我们啥时候应该使用 SNOWPIPE?

我们啥时候应该使用 save() 的高级参数?

我们啥时候应该使用 scala.util.DynamicVariable?

我们啥时候应该使用普通 BFS 而不是双向 BFS?

我们啥时候应该考虑使用私有或受保护?