前言
上周笔者工作中的程序发生了崩溃,位置是基类
调用派生类的虚函数
时,在派生类虚函数
中数据访问异常,目测是该数据成员已失效。于是便有了关于this
指针的有效性验证。
case 1
在c++里面,this
指针常传递给其他类用作回调,如类CallbackTest
重载了回调接口ICallbackTest::OnOpen
,调用这个回调的类是CB
,这种情况下传统做法一般是将类CallbackTest
的this
指针传递给CB
类,在CB
类里面调用ICallbackTest::OnOpen
。
结论
:这种情况,基本没有问题,但是当类CallbackTest
析构时就有概率会崩溃。因为在CB
调用ICallbackTest::OnOpen
时,可能会访问已经析构的数据成员。高并发时则会增加崩溃的概率。
case 2
在类里面开一个线程
调用本类的成员函数
,这种情况也和case 1
有概率会崩溃(析构时),因为在线程中调用的成员函数可能会访问已经析构的数据。如后续代码中的类Derived
和类Base
结论
-
直接传递this指针是有危险的,不管
case 1
还是case 2
都危险,请用shared_ptr
或weak_ptr
,但同时要注意shared_ptr
是非线程安全。 -
如果不使用
shared_ptr
,需要根据传递this指针的线程中
或回调中
访问的数据成员,来决定类内关联数据成员的声明顺序,确保在线程
或回调
开始前该数据成员已经声明定义,即声明顺序在前,故而析构时会在线程
或回调
结束后依赖的数据再析构。- 这种方式需要其他开发者有一样的理解才可协同开发,有难度。
-
类
构造
时,先基类构造
,再按类内数据成员声明顺序来顺序构造
。析构时,则逆序析构
。- 当基类
析构
发生时,此时调用的虚函数其实为基类的虚函数
,不会调用派生类虚函数
。该类已经退化为了基类
,派生类部分已经完成析构。
- 当基类
示例代码
该示例代码执行会崩溃(实例代码欢迎讨论)
#include <atomic>
#include <thread>
#include <functional>
#include <iostream>
#include <map>
#include <unordered_map>
#include <memory>
class Base {
public:
Base() {
t_ = std::thread([this]() {
while (!stopped) {
if (f_) {
f_();
}
}
});
}
virtual ~Base() {
stopped = true;
if (t_.joinable()) {
t_.join();
}
}
void AccestDerived() {
AccestDerivedImpl();
}
void AddF(std::function<void()> f) {
f_ = std::move(f);
}
virtual void AccestDerivedImpl() {
}
private:
int x_;
double y_;
char* z_;
protected:
std::atomic_bool stopped{false};
private:
std::function<void()> f_;
std::thread t_;
};
class Derived : public Base {
public:
Derived() : Base() {
InitData();
AddF(std::bind(&Derived::AccestDerivedImplx, this));
}
~Derived(){
stopped = true;
}
void InitData() {
map_.insert({"x", "x_val"});
map_.insert({"y", "y_val"});
map_.insert({ "z", "z_val" });
umap_["z"] = std::make_shared<std::string>("z_val");
}
void AccestDerivedImplx() {
while (!stopped) {
std::cout << "x=" << map_["x"] << std::endl;
std::cout << "y=" << map_["y"] << std::endl;
std::cout << "z=" << *umap_["z"].get() << std::endl;
}
}
Derived(const Derived&) = delete;
Derived& operator=(const Derived&) = delete;
private:
std::map<std::string, std::string> map_;
std::unordered_map<std::string, std::shared_ptr<std::string>> umap_;
};
class ICallbackTest {
public:
virtual void OnOpen(std::string xx) {
std::cout << "ICallbackTest::OnOpen" << std::endl;
}
};
class CB {
public:
CB() {}
void Init(std::shared_ptr<ICallbackTest> h) {
h_ = h;
h_thread_ = std::thread([this]() {
while (!stopped) {
auto tmp_h = h_.lock();
tmp_h->OnOpen("demo");
}
});
}
~CB() {
std::cout << "~CB()" << std::endl;
stopped = true;
if (h_thread_.joinable()) {
h_thread_.join();
}
};
private:
std::weak_ptr<ICallbackTest> h_;
std::thread h_thread_;
std::atomic_bool stopped{ false };
};
class CallbackTest : public ICallbackTest, public std::enable_shared_from_this<CallbackTest> {
public:
CallbackTest()
:
umap_{ {"demo", "demo_val"} },
cb_(),
d_(){
}
void Init() {
cb_.Init(shared_from_this());
}
~CallbackTest() {
}
void OnOpen(std::string xx) {
auto iter = umap_.find(xx);
if (iter == umap_.end()) {
exit(-1);
return;
}
std::cout << this << " CallbackTest::OnOpen "
<< iter->second << std::endl;
}
private:
CB cb_;
std::unordered_map<std::string, std::string> umap_;
Derived d_;
};
int main(int argc, char* argv[]) {
for (int i = 0; i < 1000000; i++) {
std::cout << "round " << i << " begin......" << std::endl;
auto cbt = std::make_shared<CallbackTest>();
cbt->Init();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
cbt.reset();
std::cout << "round " << i << " end........" << std::endl;
}
std::cout << "{} end" << std::endl;
return 0;
}
结尾
- 问题记录,欢迎探讨。
- 如果觉得不错,你的
点赞关注加收藏
便是笔者更新的最大动力哦^_^
。