问题起源

问题来自std::shared_ptr,为了保证shared_ptr的计数正确,只允许使用一个智能指针对象去使用裸指针构造,因此这样的代码是错误的:

1
2
3
Person* p = new Person();
std::shared_ptr<Person> p1(p);
std::shared_ptr<Person> p2(p);
其后果是p在new时候执行了一次构造,p1和p2都认为自己管理了p,因此两个共享指针独立的控制块都显示引用数为1,所以析构时各自执行一次,导致双重删除
1
2
3
Person constructor called!
Person destructor called!
Person destructor called!

显式上我们不会这么干,但是有一些隐晦的写法还是触碰到这条红线,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <memory>
using namespace std;

class Person{
public:
Person(){
cout << "Person constructor called!" <<endl;
}
~Person(){
cout << "Person destructor called!" <<endl;
}

std::shared_ptr<Person> getPersonSharedPtr(){ //通过this返回一个共享指针
return std::shared_ptr<Person>(this);
}
};

int main(){
std::shared_ptr<Person> p = std::make_shared<Person>();
std::shared_ptr<Person> p1 = p->getPersonSharedPtr();

return 0;
}
通过this裸指针返回一个共享指针是一种危险的行为,尽管我们的初衷只是获取类的同一个实例对象,执行发现析构函数同样被执行了两次。

这里错误的原理和上述是完全一样的,p完成了构造和管理,但是p1通过p的裸指针完成了管理,同样会产生两个引用数为1的控制块,造成double delete

对于本例,当然直接使p1 = p能实现正确的引用计数,但是从this获取共享指针对象还是一种美好的夙愿,例如对于插件注册系统,我们不总是可以从当前类中直接获取目标类对象,但是通过接口层可以将指针层层返回,底层所做的极其可能只是提供一个this指针;再者,在boost::asio异步回调中,良好的shared_ptr管理延长其生命周期,确保异步执行的安全性。这个问题在C++ 11得到解决。

std::enable_shared_from_this 与 shared_from_this

std::enable_shared_from_this<T>是一个类模板,要实现安全地从this获取共享指针,我们要做的就是继承这个类shared_from_thisstd::enable_shared_from_this类中一个protected函数,这个类能解决双重删除的关键在于,当我们第一次使用std::shared_ptr管理裸指针时,enable_shared_from_this类内部的weak_ptr成员会记录这个共享指针(实际上就是其控制块),调用shared_from_this的结果就是return weak_ptr.lock(),新的共享指针不会创建独立的控制块,而是仍然沿用原来共享指针的控制块,解决了双重删除的问题,一个基本使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>
using namespace std;

class Person : public std::enable_shared_from_this<Person>{ //继承
public:
Person(){
cout << "Person constructor called!" <<endl;
}
~Person(){
cout << "Person destructor called!" <<endl;
}
std::shared_ptr<Person> getPersonSharedPtr(){
return shared_from_this(); //调用父类的函数获得共享指针对象,这个对象是安全的
}
};

int main(){
std::shared_ptr<Person> p = std::make_shared<Person>();
std::shared_ptr<Person> p1 = p->getPersonSharedPtr(); //ok

return 0;
}

异步对象生命周期

在阅读一些来自boost::asio代码会发现异步任务中使用了shared_from_this的用法,主要是在异步函数中预先占用一个引用数,使得异步任务被调用前类对象不被析构,详见,用法摘要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void doWork() {
auto self(shared_from_this());
asio::post(timer_.get_executor(), [this]() {
for (int i = 0; i < 10; ++i) {
std::cout << "i=" << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Simulate work
if (timer_.expiry() <= asio::steady_timer::clock_type::now()) {
std::cout << "Work interrupted due to timeout" << std::endl;
return;
}
}
timer_.cancel(); // Cancel the timer if work completes
std::cout << "Work completed without timeout" << std::endl;
});
}

参考链接:

  1. C++里std::enable_shared_from_this是干什么用的?