最近我们遇到了一次虚函数相关的宕机,记录一下~

# 1. 问题代码

class Base
{
    public:
    Base() = default;
    virtual ~Base()
    {
        test();
    }
    
    void test()
    {
        virtualFunc();
    }
    virtual void virtualFunc() = 0;
};
class Derived : public Base
{
    public:
    Derived() = default;
    virtual ~Derived() = default;
    virtual void virtualFunc() override
    {
    }
};
int main()
{
    Derived* pClass = new Derived();
    delete pClass;
    return 0;
}

出现的问题大致在这样一段代码中,在析构函数中间接调用了虚函数。运行结果发生了错误:

h
> ./test
libc++abi.dylib: Pure virtual function called!
[1]    93942 abort      ./test

报错说调用了纯虚函数。

##2. 原因

class Base
{
    public:
    Base() = default;
    virtual ~Base()
    {
        virtualFunc();
    }
    virtual void virtualFunc()
    {
        cout << "Base destruct" << endl;
    }
};
class Derived : public Base
{
    public:
    Derived() = default;
    virtual ~Derived() = default;
    virtual void virtualFunc() override
    {
        cout << "Derived destruct" << endl;
    }
};
int main()
{
    Derived* pClass = new Derived();
    delete pClass;
    return 0;
}

上面是一段测试代码,将纯虚函数改为了普通虚函数并加上了输出。结果如下:

h
> ./test
Base destruct

可以看到程序能成功运行了。但是 main 函数中创建的虽然是派生类,析构函数中调用的 virtualFunc 却是 Base 类的。

所以我们代码宕机的原因,就是因为基类析构函数中间接调用到了某个纯虚函数,从而导致了虚函数调用错误。

# 3. 结论

经过查阅,c++ 在析构派生对象时,会有两步析构函数调用:

  1. 首先,调用派生类的析构函数;

  2. 然后,调用基类的析构函数,但在此时,c++ 已经将该对象作为派生类的特征 “抹除” 了,因此在基类的析构函数中的虚函数只会调用基类的虚函数。

对于构造函数也是一样,会先调用父类的构造函数,此时也是没有作为派生类的特征的。

因此,在构造函数和析构函数中,应该尽量避免调用虚函数。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Nirvana 支付宝

支付宝