c++ this指针的有效性

前言

上周笔者工作中的程序发生了崩溃,位置是基类调用派生类的虚函数时,在派生类虚函数中数据访问异常,目测是该数据成员已失效。于是便有了关于this指针的有效性验证。

case 1

在c++里面,this指针常传递给其他类用作回调,如类CallbackTest重载了回调接口ICallbackTest::OnOpen,调用这个回调的类是CB,这种情况下传统做法一般是将类CallbackTestthis指针传递给CB类,在CB类里面调用ICallbackTest::OnOpen

结论:这种情况,基本没有问题,但是当类CallbackTest析构时就有概率会崩溃。因为在CB调用ICallbackTest::OnOpen时,可能会访问已经析构的数据成员。高并发时则会增加崩溃的概率。

case 2

在类里面开一个线程调用本类的成员函数,这种情况也和case 1有概率会崩溃(析构时),因为在线程中调用的成员函数可能会访问已经析构的数据。如后续代码中的类Derived和类Base

结论

  1. 直接传递this指针是有危险的,不管case 1 还是 case 2都危险,请用shared_ptrweak_ptr,但同时要注意shared_ptr是非线程安全。

  2. 如果不使用shared_ptr,需要根据传递this指针的线程中回调中访问的数据成员,来决定类内关联数据成员的声明顺序,确保在线程回调开始前该数据成员已经声明定义,即声明顺序在前,故而析构时会在线程回调结束后依赖的数据再析构。

    1. 这种方式需要其他开发者有一样的理解才可协同开发,有难度。
  3. 构造时,先基类构造,再按类内数据成员声明顺序来顺序构造。析构时,则逆序析构

    1. 当基类析构发生时,此时调用的虚函数其实为基类的虚函数,不会调用派生类虚函数。该类已经退化为了基类,派生类部分已经完成析构。

示例代码

该示例代码执行会崩溃(实例代码欢迎讨论)
#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;
}

结尾

  • 问题记录,欢迎探讨。
  • 如果觉得不错,你的点赞关注加收藏便是笔者更新的最大动力哦^_^
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值