You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨线程通过std::shared_ptr调用偶现纯虚函数调用问题求解

问题原因

你遇到的纯虚函数调用核心是C++对象析构阶段的虚函数表切换规则,结合多线程时序竞态共同导致的:

  1. C++派生类对象的析构严格按「派生类自身析构→按继承声明顺序依次析构基类」的顺序执行,每进入一个基类的析构阶段,对象的虚函数表就会切换为当前基类对应的版本,不再保留派生类的实现。
  2. 你的Receiver类按顺序继承了Thread和Sender::Signal两个基类,析构时会先执行Receiver自身的析构,再进入Thread基类的析构,等Thread析构完成后,最后进入Sender::Signal基类的析构阶段。此时对象的虚表已经切换为Sender::Signal的版本,而Sender::Signal::send()是纯虚函数,如果此时被调用就会触发错误。
  3. 时序竞态窗口出现在receiver.reset()调用后:Receiver的强引用计数归零,启动析构流程,在Thread::~Thread()中等待自身线程退出的这段时间里,Sender的工作线程刚好调度执行notify(),如果此时weak_ptr.lock()刚好在强引用计数归零前的瞬间拿到了临时强引用,就会获取到指向Sender::Signal基类的指针,等实际执行send()调用时,对象已经完成了Receiver和Thread部分的析构,进入Signal基类的析构阶段,虚表已经切换为纯虚版本,就会触发崩溃。
修复方案

你可以通过以下任意一种方式规避问题:

  • 调整继承顺序,将Sender::Signal放在继承列表的第一位:class Receiver : public Sender::Signal, public Thread,这样析构时会先析构Signal基类,再进入Thread的析构等待线程退出,避免后续出现虚表切换后还被调用的情况。
  • 给Sender增加移除接收者的接口,在Receiver析构的最开始先将自己从Sender的接收者列表中移除,保证不会有后续的调用请求。
  • 不要在基类Thread的构造函数中直接启动线程,改为对外提供独立的start()方法,等派生类完全构造完成后再调用启动线程,避免构造阶段就出现虚函数访问风险。

内容的提问来源于stack exchange,提问作者Alex Chudinov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 12:36:04