问题
这是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 的工作原理),真正释放连接对象。