现象
程序崩溃,报错:pure virtual method called,terminate called without an active exception,大意为调用了纯虚函数,程序中止。
原因分析
纯虚函数是没有函数体的虚函数。包含纯虚函数的类就叫抽象类。C++不允许抽象类实例化对象,正常情况下不会执行抽象类的纯虚函数。
虚函数通过函数表实现多态,在一些特殊情况下(不安全的代码写法)会发生纯虚函数的调用。
通常情况下在构造和析构的时候容易报"pure virtual method called"错误,这与基类和派生类的构造函数和析构函数的执行顺序有关。
构造函数执行顺序:
1、先初始化基类:将对象的虚函数指针指向基类虚函数表,初始化基类成员变量,调用基类构造函数;
2、再初始化派生类:将对象的虚函数表指针指向派生类虚函数表,初始化派生类成员变量,调用派生类构造函数。
析构顺序和构造顺序相反:
1、先析构派生类:对象的虚表指针指向的是派生类的虚函数表,调用派生类析构函数,执行派生类成员变量的析构;
2、再析构基类:将虚表指针指向基类虚函数表,调用基类析构函数,析构基类成员变量。
根据上面的流程,构造函数和析构函数执行过程中,都有一段时间对象的虚函数指针指向基类虚函数表,如果在构造或者析构没有完成的时候调用了该对象的虚函数,则是调用了基类的纯虚函数。这种情况一般发生在多线程调用,构造或析构在一个线程,虚函数调用在另一个线程。
解决思路
1、在调用对象的函数时,对象指针进行有效性判断(p==NULL);
2、不要再构造函数和析构函数中执行睡眠操作;
3、对对象指针加锁;
4、先停止线程,sleep,再执行析构,留一个时间隔离度,不要在析构函数里停止线程。
Qt复现pure virtual method called报错demo
运行后报错pure virtual method called,terminate called without an active exception,程序crash
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
//基类
class Cheese{
public:
Cheese(){};
virtual ~Cheese(){QThread::msleep(200);};//基类析构函数执行了睡眠
virtual void func() = 0;//纯虚函数
};
//派生类
class Cake : public Cheese{
public:
Cake(){};
virtual ~Cake(){};
virtual void func(){};//虚函数
};
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
Cake *pCake;
pthread_t ntid;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <pthread.h>
void* thread_func(void* p)
{
Cake *tCake = (Cake*)p;
delete tCake;
return NULL;
}
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) , ui(new Ui::MainWindow)
{
ui->setupUi(this);
pCake = new Cake;
pthread_create(&ntid,NULL,thread_func,pCake);//在子线程释放pCake
QThread::msleep(20);//睡眠等待进入析构睡眠
//多线程调用时,如果在别的线程执行了析构,当前线程还在继续调用pCake,则会报错
//可以做pCake==NULL判断,避免普通的crash
//但是如果基类的析构函数执行时间较长(比如在sleep),此时pCake!=NULL,还会执行下面的函数,此时就会pure virtual method called
pCake->func();
}
MainWindow::~MainWindow()
{
delete ui;
}