关于 enable_shared_from_this

一. 引入

简单地说:enable_shared_from_this 是为了解决 在类的内部获取自己的 shared_ptr 这件事情而存在的。

众所周知,每一个对象都能通过 this 指针来访问自己的地址。this 指针也是所有成员函数的隐含参数。然而有些时候,我们需要的不仅是 this,而是一个 “this 的智能指针”。

这里有一个常见的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A {
public:
A() : did_it_(false) {}
~A() {
std::cout << "destoried" << std::endl;
}

void OnDo(bool did) {
did_it_ = did;
std::cout << "somthing did" << std::endl;
}

void DoSth_Async() {
std::thread t([this]() {
std::this_thread::sleep_for(std::chrono::seconds(5));
//...do somthing
OnDo(true);
});
t.detach();
}

private:
bool did_it_;
};

代码如上:在异步方法 DoSth_Async() 中调用了成员方法 OnDo(bool)。这里存在一个问题:当 OnDo() 被调用的时候,该类的对象是否还在存活?

1
2
3
4
5
6
7
8
int main(){
{
std::shared_ptr<A> ptr(new A());
ptr->DoSth_Async();
}
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}

智能指针 ptr 在出作用域后立即被释放。所以当 OnDo() 被调用的时候,其所在的对象实际已经被释放了。如果要确保在 OnDo() 被调用的时候,该对象仍然在生命周期内呢?

一个方便的方法就是在构建线程的时候,将该对象的 shared_ptr 传入到线程中。在该线程的生命周期内,该对象就会一直存在。这是一种利用 shared_ptr 的 保活机制

此时,enable_shared_from_this 就有存在的必要了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A : public std::enable_shared_from_this<A> {
public:
A() : did_it_(false) {}
~A() {
std::cout << "destoried" << std::endl;
}

void OnDo(bool did) {
did_it_ = did;
std::cout << "somthing did" << std::endl;
}

void DoSth_Async() {
auto self = shared_from_this();
std::thread t([this, self]() {
std::this_thread::sleep_for(std::chrono::seconds(3));
//...do somthing
OnDo(true);
});
t.detach();
}

private:
bool did_it_;
};

enable_shared_from_this 是一个模板类。它一般用作基类,它的成员 shared_from_this()weak_from_this() 可以使继承此类的类从当前对象获取其本身的 shared_ptr 或者 weak_ptr 并且增加引用计数。


我们直接使用 this 指针来构建自身的 shared_ptr 不可以吗?就像下面代码所表现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class C {
public:
std::shared_ptr<C> GetSelf() {
return std::shared_ptr<C>(this);
}

void DoSomthing() {
auto ptr = GetSelf();
std::cout << ptr.use_count() << std::endl;
}
};

int main() {
std::shared_ptr<C> ptr_c(new C());
ptr_c->DoSomthing(); // print 1
std::cout << ptr_c.use_count() << std::endl; // print 1
return 0;
}

这种方法在使用的时候可能看不出问题,但是在对象析构的时候将会出现问题:一个对象将被释放两次。在 DoSomthing() 方法结束后它将释放一次,在 main() 函数完成后又将释放一次。

究其原因,GetSelf() 构造智能指针时,其引用计数并没有自增。


二. 原理

enable_shared_from_this 位于 <memory> 头文件中,其实现非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// CLASS TEMPLATE enable_shared_from_this
template<class _Ty>
class enable_shared_from_this {
public:
using _Esft_type = enable_shared_from_this;

shared_ptr<_Ty> shared_from_this() {
return shared_ptr<_Ty>(_Wptr);
}

shared_ptr<const _Ty> shared_from_this() const {
return shared_ptr<const _Ty>(_Wptr);
}

weak_ptr<_Ty> weak_from_this() noexcept {
return _Wptr;
}

weak_ptr<const _Ty> weak_from_this() const noexcept {
return _Wptr;
}

protected:
constexpr enable_shared_from_this() noexcept : _Wptr() {}
enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {}
enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept {
return *this;
}
~enable_shared_from_this() = default;

private:
template<class _Other, class _Yty>
friend void _Enable_shared_from_this1(const shared_ptr<_Other>& _This, _Yty* _Ptr, true_type);

mutable weak_ptr<_Ty> _Wptr;
};

其中友元 _Enable_shared_from_this1() 会被 shared_ptr 调用。在这个友元函数中,会尝试着给私有成员 _Wptr 赋值。

shared_from_this() 被调用时,使用 _Wptr 构造一个 shared_ptr,此时引用计数加 1。


三. 陷阱

1. 在原生指针的对象里调用了 shared_from_this

如下代码:

1
2
A* a = new A();
a->DoSth_Async();

由于没有使用 shared_ptr,友元 _Enable_shared_from_this1() 不会被调用,此时 _Wptr 是 empty 的。如果强行调用 shared_from_this() 将会引发异常:

1
exception : std::bad_weak_ptr

2. 过早调用 shared_from_this()

代码如下:在构造函数中调用了 shared_from_this(),此时还没有给 _Wptr 赋值,会引发同样的异常。

1
2
3
4
5
6
class Sample : public std::enable_shared_from_this<Sample> {
public:
Sample() {
auto ptr = shared_from_this(); // 异常:bad_weak_ptr
}
};

3. 关于继承

在一棵继承树里重复继承 std::enable_shared_from_this 会引发 编译错误

如果需要在子类中使用 shared_from_this(),可以这样写:

1
2
3
4
5
6
7
8
9
10
11
class Super : public std::enable_shared_from_this<Super> {
public:
virtual ~Super() {} // 非常重要!!!
};

class Sub : public Super {
public:
std::shared_ptr<Sub> shared_from_this() {
return std::dynamic_pointer_cast<Sub>(Super::shared_from_this());
}
};