加载中...
std::enable_shared_from_this 的存在意义?
第1节:代码改善:一个“坑爹”的文字类冒险游戏
第2节:在禁止多重继承的情况下,如何设计“直立智慧猩猩”类?
第3节:C++多线程代码中的“乱序”执行现象
第4节:C++中函数指针有什么作用呢?
第5节:为什么我用c++写的游戏那么简陋?
第6节:多线程读写socket导致的数据混乱的原因是什么?
第7节:WebSocket 是什么原理?为什么可以实现持久连接?
第8节:怎样在c++中实现instanceof?
第9节:一个函数多处 return 是好风格吗?
第10节:C++中虚函数相比非虚函数的优势
第11节:为什么 C::C::C::C::foo() 能编译成功?
第12节:如何静态反射C++枚举的名字
第13节:看C++大叔如何拥 java 妹子入怀……
第14节:坨——理解递归实现“汉诺塔”代码的关键
第15节:C++编译器如何实现 const(常量)?
第16节:C++如何为断言加上消息
第17节:初学C++到什么水平,算是合格的初级开发工程师?
第18节:C++编程要避免使用单例模式吗?
第19节:学习C++要学boost库吗?
第20节:C++的继承就是复制吗?
第21节:C++构造函数失败,如何中止创建对象?
第22节:C++学完多线程后,学什么呢?
第23节:string_view 适合用做函数的返回值类型吗?
第24节:为指针取别名,为何影响const属性?
第25节:std::enable_shared_from_this 的存在意义?
课文封面

传递 shared_ptr 指针时,如果临时以祼指针传递,容易造成共享关系的断裂,this 也是裸指针,所以,当需要在 class 内部,将 this 以 shared_ptr<T> 的方式传递时,你就需要 std::enabled_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,为啥不直接像下面这么写呢?

struct X {}; int main() { shared_ptr<X> p(new X); shared_ptr<X> q = p; // 为什么不直接这么复制? assert(p == q); // p and q must share ownership assert(!(p < q || q < p)); }

像上面代码中注释的那一行,直接复制,这不更符合我们使用shared_ptr的习惯吗?

回答

说说 shared_ptr 的“传递链”

我来说个简单版本:就是为了不断了 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 基于裸指针创建。

this 也是裸指针

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

现在再来读 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身上来,就是:

// demo 毫无变化 : 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 的工作原理),真正释放连接对象。