背景
当程序中创建了多个对象并且是多个类型的对象时,需要理解这些对象的生命周期(构造和析构)是什么关系至关重要
基本原则
按照构造顺序,逆序的析构;可以类比与栈的操作,先入栈的后出栈;thread_local对象跟随所属线程第一次执行到构造,线程结束后析构
对象类型:全局对象,静态全局对象,局部对象,静态局部对象,类成员对象,thread_local对象
#include <thread>
#include <iostream>
#include <chrono>
#include <ctime>
#include <memory>
#include <vector>
class A {
public:
A() { std::cout << "A()" << std::endl;}
~A() { std::cout << "~A()" << std::endl;}
};
class B {
public:
static B& instance() {
static B b; // 局部静态对象
return b;
}
B() { std::cout << "B()" << std::endl;}
~B() { std::cout << "~B()" << std::endl;}
};
class C {
public:
C() {
a_ = std::make_shared<A>();
B::instance();
std::cout << "C()" << std::endl;
}
~C() { std::cout << "~C()" << std::endl;}
std::shared_ptr<A> a_; // 类成员对象
};
class D {
public:
D() { std::cout << "D()" << std::endl;}
~D() { std::cout << "~D()" << std::endl;}
};
class E {
public:
E() { std::cout << "E()" << std::endl;}
~E() { std::cout << "~E()" << std::endl;}
};
class F {
public:
F() { std::cout << "F()" << this << std::endl;}
~F() { std::cout << "~F()" << this << std::endl;}
};
void f() {
thread_local F f; // thread_local对象
}
D d; // 全局对象
static E e; //静态全局对象
int main() {
std::cout << "main begin" << std::endl;
C c; // 局部对象
bool running1 = true;
std::thread t1([&running1](){
std::cout << "t1 is running" << std::endl;
{
f();
f();
}
while(running1){}
std::cout << "t1 is dead" << std::endl;
});
bool running2 = true;
std::thread t2([&running2](){
std::cout << "t2 is running" << std::endl;
{
f();
f();
}
while(running2){}
std::cout << "t2 is dead" << std::endl;
});
running1 = false;
running2 = false;
if(t1.joinable()) {
t1.join();
}
if(t2.joinable()) {
t2.join();
}
std::cout << "main end" << std::endl;
}
输出
D()
E()
main begin
A()
B()
C()
t2 is running
F()0x7215905fe630
t2 is dead
~F()0x7215905fe630
t1 is running
F()0x721590dff630
t1 is dead
~F()0x721590dff630
main end
~C()
~A()
~B()
~E()
~D()
案例(程序退出时崩溃)
现象
在一个类的成员方法中,将this通过lamda函数注册给一个异步回调;然后回调执行的时候this被析构
代码示例
比如如下这个代码
#include <iostream>
#include <memory>
#include <functional>
#include <thread>
bool block = true;
class Base{
public:
virtual void func() = 0;
};
class Derived : public Base {
public:
Derived() {
p = new int(1);
}
~Derived() {
std::cout << "~Derived" << std::endl;
delete p;
p = nullptr;
}
void func() {
std::cout << "call func, *p:" << *p << std::endl; // 如果p是空指针那么就会crash
}
int* p;
};
class MyClass {
public:
void doAsyncWork() {
// 创建一个weak_ptr来捕获this指针
// 注册一个lambda作为异步回调
std::function<void()> callback = [this]() {
while(block){}
doWork();
};
// 模拟异步操作(比如另外一个线程执行回调)
std::thread(callback).detach();
}
// 为了演示目的,定义一个成员函数
void doWork() {
std::cout << "Doing some work" << std::endl;
d.func();
}
~MyClass() { std::cout << "~MyClass" << std::endl;}
Derived d;
};
int main() {
// 创建一个MyClass实例的shared_ptr
{
std::shared_ptr<MyClass> instance = std::make_shared<MyClass>();
// 调用doAsyncWork注册异步回调
instance->doAsyncWork();
}
block = false; // 原来的这个对象析构后,异步回调再执行
// 等待足够的时间让异步回调完成,避免main函数立刻退出
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
我们MyClass的成员函数doAsyncWork中注册了一个异步回调callback,这个callback中当解除block时会调用MyClass成员对象d的func函数;
上述当MyClass析构时,然后异步回调再被执行,那么访问成员对象d已经析构,这里会产生未定义行为,为了凸显这未定义行为,我们在对象d的func函数中访问一个指针(析构时会设置为nullptr),这时将会直接crash。
原因
在异步回调中访问了已经析构的对象。
解决办法
异步回调中的lambda函数捕获的只是this指针,这个对象生命周期不可控了;需要想办法将这个对象的声明周期延长到这个异步回调里;
这个是时候大名鼎鼎的enable_shared_from_this就来了,在成员函数中通过shared_from_this()返回一个智能指针,用来判断和延长对象的生命周期
改动如下,让MyClass继承enable_shared_from_this,然后通过shared_from_this+弱指针进行判断和延长MyClass对象的生命周期
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void doAsyncWork() {
// 创建一个weak_ptr来捕获this指针
std::weak_ptr<MyClass> weak_self = shared_from_this();
// 注册一个lambda作为异步回调
std::function<void()> callback = [weak_self]() {
while(block){}
// 在callback中,尝试从weak_ptr升级到shared_ptr
std::shared_ptr<MyClass> self = weak_self.lock();
if (self) {
// 如果self不为nullptr,说明原始对象还活着,可以安全调用
self->doWork();
} else {
// 原始对象已经销毁
std::cout << "Object was destroyed, cannot call member function." << std::endl;
}
};
// 模拟异步操作(比如另外一个线程执行回调)
std::thread(callback).detach();
}
// 为了演示目的,定义一个成员函数
void doWork() {
std::cout << "Doing some work" << std::endl;
d.func();
}
~MyClass() { std::cout << "~MyClass" << std::endl;}
Derived d;
};
请注意,在实际应用中,根据异步操作的具体情况和库的选择,注册异步回调的方式可能有所不同,但基本思路是相同的——使用 std::weak_ptr 以防止循环引用并在回调中检查对象是否仍然存在。
一些coredump堆栈
#0 0x00007f0445877438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f044587903a in __GI_abort () at abort.c:89
#2 0x00007f04461bb84d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007f04461b96b6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007f04461b9701 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007f04461ba23f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
当看到__cxa_pure_virtual这个堆栈时,如果这个类的纯虚函数已经被实现了,那么大概率是这个对象已经损坏了(析构也算,它的虚函数表已经损坏了)。
或者直接访问了空指针