std::enable_shared_from_this 有什么意义?

问:

这是boost里面举的一个例子:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

我的问题是:既然要专门定义一个类似f()的函数来获取另一个shared_ptr,为啥不直接像下面这么写呢?这不更符合我们使用 shared_ptr 的习惯吗?

int main()
{
    shared_ptr<X> p(new X);
    shared_ptr<X> q = p;
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

 

我来说个简单版本:就是为了不断了 shared_ptr 的传递链。

shared_ptr 是 “共享”型智能指针,但每次共享,都必须是复制已有的 shared_ptr ,比如:

 例1 (正确用法) ///

// 创建:
std::shared_ptr<int> sp_0 { new int(999) }; 
// 开始共享:
std::shared_ptr<int> sp_1 = sp_0; // 复制源 shared_ptr 以实现共享

现在,sp_1 和 sp_0 就 互相“shared”了。而sp_1 也如前所述,是基于已有的 shared_ptr,也就是 sp_0 实现共享的。你可以把 " sp_1 = sp_0 " 这样的代码,理解为是两个智能指针借此,实现了互相通气,从而建立了“互相知情”的共享。

如果中间断开,硬是从原始的裸指针——虽然肯定也是同一个指针,但却不叫“共享”了:

 例2 (错误用法) ///

// 创建:
std::shared_ptr<int> sp_0 { new int(999) }; 
// 开始“共享”:
std::shared_ptr<int> sp_1 { sp_0.get() }; // sp_1 基于祼指针创建

例2代码也能顺利编译,但运行时,有可能得到类似如下的输出:

free(): double free detected in tcache 2

原因就是 sp_1 和 sp_0 并没有实现共享,而是各自以为自己管理着某块内存,但它们其实管理的是同一块内存。可以把这种关系,理解为“互不知情”的共享——就是骗婚的那种,一个骗婚的女人,可能可以同时有6个老公,这6个老公本质是在共享,但完蛋就完蛋在“互不知情”。

为什么互不知情?因为 新的智能指针是基于裸指针建立的:

std::shared_ptr<int> sp_1 { sp_0.get() };

// 等同于:

int* tmp = sp_0.get();
std::shared_ptr<int> sp_1 {tmp}; // tmp 是裸指针

这就叫 shared_ptr 在传播的过程中掉链子了:某个新的 shared_ptr 竟然基于 裸 指针创建。

C++ class 中的 this,也是一个指针——更准确地讲是: this 也是一个“裸”指针。

那如果某个类的某个方法,要从 this 指针 创建一个新的 shared_ptr 怎么办?

this 是裸指针,所以如果基于它来创建一个新的shared_ptr,传播链必断啊!

比如:

struct A 
{
     std::shared_ptr<A> NewSharedPtr()
     {
          return std::shared_ptr<A>(this); // 基于 this 创建一个 share_ptr
     }
};

void demo()
{
    // 创建
    shared_ptr<A> sp_0 {new A}; 

    // 试图传播……
    auto sp_1 = sp_0->NewSharedPtr(); // 掉链子了
}

sp_1 和 sp_0,现在又是“互不知情”的共享了。掉链就发生在 NewSharedPtr() 这个方法里面:使用裸指针(在本例中,也就是 A* )创建了一个新的 shared_ptr<A> ,它将和之前任意一个 shared_ptr<A> 都互不知情,它以为自己在创建时,是独立拥有 this。

现在再来读 enable - shared - from - this 这个类名,就能明白不少吧:允许-共享-从-this。它就是一个工具(以基类的形式呈现),用于让我们不打断“shared”链,安全地创建出一个当前持有 this 对象的所有 shared_ptr 互相知情地共享的新 shared_ptr 。

更具体地说,enable_shared_from_this 基类将带来一个方法,叫 “shared_from_this()”,它的返回值就我们想要的。

结合上面例子,解决问题的方法,就是 为 struct A 加个基类 enable_shared_from_this :

struct A : public std::enable_shared_from_this<A>
{
     std::shared_ptr<A> NewSharedPtr()
     {
          return shared_from_this(); // 这回安全了
     }
};

// demo 毫无变化 :
void demo()
{
    // 创建
    shared_ptr<A> sp_0 {new A}; 

    // 试图传播……
    auto sp_1 = sp_0->NewSharedPtr(); // 成功!
}

当然啦,调用本例NewSharedPtr() ,从而最终调用:

enable_shared_from_this<A>::shared_from_this();

也得有注意事项:你当然得通过一个现有的shared_ptr<A> 来调用该方法,也就是最开始时的那一步“创建”第一个智能指针的步骤不可忽略,否则如何无中生有地生出第一个智能指针对象呢?

但是,以上都是你已经懂的,因为你问的例子,套到A身上来,就是:

void demo()
{
    // 创建
    shared_ptr<A> sp_0 {new A}; 

    // 试图传播……
    auto sp_1 = sp_0; // 这样写,不比 sp_0->NewSharedPtr() 直观,简捷100倍吗?
}

对啊,为什么不直接写 sp_1 = sp_0 ,而非要写 sp_1 = sp_0->NewSharedPtr() 啊???

两个常见原因。

第一个原因是,有时候我们每当对外“分享”一次,就会额外在指针身上做一些事情,比如,记录一下 分享时的美好的心情:

struct A : public std::enable_shared_from_this<A>
{
     std::shared_ptr<A> NewSharedPtr()
     {
          std::cout << "分享是人类进步的台阶……\n" ;

          auto sp = shared_from_this(); // 这回安全了

          this->doSomethingAfterShared(); //                     

          return sp;
     }

private:
     void doSomethingAfterShared(); 
};

第二原因,也是主要原因:有时候必须在类的方法往别的方法或函数传递一个新的分享,特别是异步操作时的回调。

假设有个 网络连接类:

// 有业务的伪代码:
class Connection : public std::enable_shared_from_this<A>
{
     
public:
     void OnAccept()
     {
          std::shared_ptr<Connection> self { shared_from_this();};

         // 开始异步读,读到数据后,会调用 OnRead
          _socket.ReadSomeByte(buf, self, OnRead);     
     }

    void OnRead() {};
};

和A的例子区别在于: OnAccept() 方法中,传播的 self 并不用于 return,而是用于传递给别的方法,包括外部自由函数。这时候,这个传递就只能在 类的方法里执行,而在类的方法里,我们就真的只有 this 了,而没有 最初的那个源 shared_ptr,比如前面几个例子中的 sp_0.

具体到网络服务端异步处理,还有一个非常明确的需求:通过不断传递 shared_ptr,来确保这个 Connection 裸 指针一直存活,直到不做传递了,才(借助 shared_ptr 的工作原理),真正释放连接对象。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值