关于 shared_ptr
shared_ptr是一种共享所有权的智能指针,它允许我们安全地访问和管理对象的生命周期。shared_ptr的多个实例通过共享控制块结构来控制对象的生命周期。
控制块维护了引用计数(reference count),弱引用计数(weak count)和其他必要的信息,通过这些信息,控制块能够确定一个对象在内存中是否可以被安全销毁。
当使用原始指针构造或者初始化一个shared_ptr时,将会创建一个新的控制块。为了确保一个对象仅由一个共享的控制块管理,必须通过复制已存在的shared_ptr对象来创建一个新的shared_ptr实例,例如:
1void good()
2{
3 auto p{new int(10)}; // p is int*
4 // create additional shared_ptr from an existing shared_ptr
5 std::shared_ptr<int> sp1{p};
6 // sp2 shares control block with sp1
7 auto sp2{sp1};
8}
而使用指向已由shared_ptr管理的对象的原始指针来初始化另一个shared_ptr时,会创建一个新的控制块来管理该对象,
这样同一个对象就同时被多个控制块管理,这会导致 undefined behavior,例如:
1void bad()
2{
3 auto p{new int(10);};
4 std::shared_ptr<int> sp1{p};
5 std::shared_ptr<int> sp2{p}; // Undefined behavior!
6}
通过原始指针的方式实例化shared_ptr很容易产生同一个原始指针实例化多个shared_ptr这样的编码疏忽,从而造成严重后果。
因此尽量使用std::make_shared或者std::allocate_shared来降低出错的可能性。毕竟除非有人刻意为之,否则我们似乎很难遇到或写出这样的代码:
但是在某些情况下,shared_ptr管理的对象需要为自己获取shared_ptr,我会在后面的篇幅中重点讲解这种情况。
但是首先需要说明的是,类似于下面这样尝试从自身指针创建shared_ptr的方式是行不通的:
1struct Egg
2{
3 std::shared_ptr<Egg> get_self_ptr()
4 {
5 return std::shared_ptr<Egg>(this);
6 }
7};
8
9void spam()
10{
11 auto sp1 = std::make_shared<Egg>();
12 auto sp2 = sp1->get_self_ptr(); // undefined behavior
13 // sp1 and sp2 have two different control blocks managing same Egg
14}
为了解决这个问题,我们就需要用到std::enable_shared_from_this。public 继承std::enable_shared_from_this
的类可以通过调用shared_from_this()方法来获取自身的shared_ptr,下面是一个简单的例子:
1struct Thing;
2void some_api(const std::shared_ptr<Thing>& tp);
3
4struct Thing : public std::enable_shared_from_this<Thing>
5{
6 void method()
7 {
8 some_api(shared_from_this());
9 }
10};
11
12void foo()
13{
14 auto sp = std::make_shared<Thing>();
15 sp->method();
16}
为什么要从 this 创建 shared_ptr
让我们来看一个更有说服力的例子,在这面的例子中,shared_ptr管理的对象需要为自己获取一个shared_ptr。
一个Processor类异步处理数据并将其保存到数据库。在接收数据时,Processor通过自定义执行器来异步处理数据:
1class Executor
2{
3public:
4 void execute(const std::function<void(void)>& task);
5
6private:
7 // ...
8};
9
10class Processor
11{
12public:
13 void process_data(const std::string& data);
14
15private:
16 void do_process_and_save(const std::string& data) {
17 // process data
18 // sava data to DB
19 }
20
21private:
22 Executor* executor_;
23};
Processor类从一个Client类接收数据,这个Client持有该Processor的一个shared_ptr实例:
1class Client
2{
3public:
4 void some_method()
5 {
6 processor_->process_data("xxxxxx");
7 }
8
9private:
10 std::shared_ptr<Processor> processor_;
11}
Executor是一个线程池,它封装了多个线程和一个任务队列,并从队列中执行不同的 task。
在Processor::process_data中,我们需要将执行过程包装成 task 传递给Executor。在 task 中调用私有方法do_process_and_save,
该方法在将数据保存到数据库之前对数据进行处理。因此,构造 task 的时候,需要捕获对Processor对象本身的引用:
1void Processor::process_data(const std::string& data)
2{
3 executor_->execute([this, data]() {
4 // ...
5 do_process_and_save(data);
6 });
7}
但是,Client可以出于各种原因随时将shared_ptr丢弃或者重置为其他关联的Processor,这可能会破坏原来的Processor。
因此在执行排队的 lambda 之前或期间,捕获的this指针可能会失效。
我们可以通过在 lambda 中捕获 this 对象的 shared_ptr 来避免上面的 undefined behavior 的发生。
只要排队的 lambda 持有一个Processor的shared_ptr,Processor就会保持正常的运行状态。然而,我们知道,单纯的像这样创建一个shared_ptr<Processor>(this)是行不通的。
我们需要一种机制,让一个shared_ptr管理对象以某种方式控制它的控制块,从而获取另一个自身的shared_ptr对象。而使用std::enable_shared_from_this就是为了达到了这个目的:
1class Processor : public std::enable_shared_from_this
2{
3 // ...
4};
5
6void Processor::process_data(const std::string& data)
7{
8 executor_->execute([self = shared_from_this(), data]() {
9 // ...
10 self->do_process_and_save(data);
11 });
12}
为什么要使用 enable_shared_from_this
本质上,为一个已经被shared_ptr管理的指针对象创建额外的shared_ptr实例只能通过从可以访问控制块的 handle 生成。
该 handle 可以是一个shared_ptr,也可以是一个weak_ptr。如果一个对象有这个 handle,那么它就可以为自己创建额外的shared_ptr。
但是shared_ptr 是一个强引用,会影响受管理对象的生命周期。将shared_ptr保存到自身对象将会导致自身永远无法被释放,从而发生内存泄漏:
解决这个问题可以通过weak_ptr来实现。weak_ptr是一种弱引用,它不会影响受管理对象的生命周期,但是在需要时可以用来获取强引用。
如果一个对象持有自身的weak_ptr,那么在需要的时候,就可以获取自身的shared_ptr:
1class Naive
2{
3public:
4 static std::shared_ptr<Naive> create()
5 {
6 auto sp = std::shared_ptr<Naive>(new Naive);
7 sp->weak_self_ = sp;
8 return sp;
9 }
10
11 auto async_method()
12 {
13 return std::async(std::launch::async, [self = weak_self_.lock()]() {
14 self->do_something();
15 });
16 }
17
18 void do_something()
19 {
20 std::this_thread::sleep_for(std::chrono::seconds(1));
21 }
22
23private:
24 Naive() {}
25 Naive(const Naive&) = delete;
26 const Naive& operator=(const Naive&) = delete;
27 std::weak_ptr<Naive> weak_self_;
28};
29
30void test()
31{
32 std::future<void> ft;
33 {
34 auto pn = Naive::create();
35 ft = pn->async_method();
36 }
37 ft.get();
38}
上面的实现不够完美,因为它有很多限制。我们需要确保在构造Naive类的时候初始化对自身的weak_ptr,因此Naive的构造必须仅通过静态工厂方法来进行约束。
这种解决方案对合理性需求施加了太多的限制,然而这个实现却为标准解决方案std::enable_shared_from_this创建了一个概念框架。
std::enable_shared_from_this 的内部实现
std::enable_shared_from_this的典型实现是一个只包含了weak_ptr<T>字段的类,通常这个字段叫做weak_this(clang 中为__weak_this_,gcc 中为_M_weak_this):
1template<class T>
2class enable_shared_from_this
3{
4 mutable weak_ptr<T> weak_this;
5
6public:
7 shared_ptr<T> shared_from_this()
8 {
9 return shared_ptr<T>(weak_this);
10 }
11
12 // const overload
13 shared_ptr<const T> shared_from_this() const
14 {
15 return shared_ptr<const T>(weak_this);
16 }
17
18 // .. more methods and constructors..
19 // there is weak_from_this() also since c++17
20
21 template <class U> friend class shared_ptr;
22};
std::enable_shared_from_this的核心代码就这么简单,而剩下的魔法代码在shared_ptr的构造函数中实现。
当shared_ptr用T*初始化的时候,如果T是从std::enable_shared_from_this<T> 继承来的,则shared_ptr构造函数会初始化weak_this。
只有当T从std::enable_shared_from_this 公开 继承的时候,才能在编译时使用 trait 类(std::enable_if和std::is_convertible)来启用weak_this的初始化代码。
因此,必须使用public继承std::enable_shared_from_this类,因为shared_ptr构造函数需要通过weak_this来进行初始化,如果不 public 继承,则会在运行时抛出bad_weak_ptr异常。
关于std::enable_shared_from_this还有一些值得注意的细节:
weak_ptr被声明为 mutable,因此它也可以被修改为 const 对象shared_ptr被声明为友元类型,这样它就可以访问私有字段weak_this
下面是另一个简单的例子,用来描述这一切是如何联系在一起的。我们将初始化shared_ptr的代码故意分为两个步骤,以演示嵌入的weak_ptr的创建和初始化的两个阶段:
1struct Article : public std::enable_shared_from_this<Article>
2{
3};
4
5void foo()
6{
7 // step 1
8 // Enclosed 'weak_this' is not associated with any control block.
9 auto pa = new Article;
10
11 // step 2
12 // Enclosed 'weak_this' gets initialized with a control block
13 auto spa = std::shared_ptr<Article>(pa);
14}
关于 std::bad_weak_ptr 异常
调用shared_from_this方法有一个限制,就是只能在shared_ptr管理的对象内部调用。
从 c++17 开始,在不受shared_ptr管理的对象内部调用shared_from_this会触发std::bad_weak_ptr异常,而在 c++17 之前,这种操作是 undefined behavior。
1struct Article : public std::enable_shared_from_this<Article>
2{
3 void foo()
4 {
5 auto self = shared_from_this();
6 // ...
7 }
8}
9
10void test()
11{
12 auto pa = new Article;
13 pa->foo(); // ! std::bad_weak_ptr
14}
当shared_from_this调用的时候,如果weak_ptr未初始化或者已过期,那么shared_ptr的构造函数就会抛出异常。
另一个触发抛出std::bad_weak_ptr异常的情况是,当在构造函数中调用shared_from_this的时候,因为当前对象还没构造完,嵌入的weak_this尚未初始化而导致抛出异常。
触发std::bad_weak_ptr异常的另一个非常微妙而且难以定位排查的情况是,一个类没有使用public继承std::enable_shared_from_this类并且调用了shared_from_this方法。
private 和 protected 继承都会阻止weak_this成员的初始化,这可能会在没有任何编译器警告的情况下被忽略。例如:
1class Overlooked : std::enable_shared_from_this<Overlooked>
2{
3public:
4 void foo()
5 {
6 // std::bad_weak_ptr
7 auto self = shared_from_this();
8 }
9}
有些环境下会禁用或者避免抛出异常,对于这种情况,从 c++17 开始,就有了一种替代shared_from_this的方法。
c++17 将weak_from_this方法添加到std::enable_shared_from_this中,后者返回嵌入的weak_this的副本。
shared_ptr可以安全地从该weak_ptr中获取,而不会导致任何std::bad_weak_ptr异常的发生:


评论