绝对不要在 构造函数/析构函数 中调用虚函数


今天为了查一个重复delete的bug,在析构函数中调用了一个虚函数 toString,想在对象析够前打印对象信息,结果发现打印出来全都是基类的,后来仔细研究了这个问题,先说结论:

 

1,绝对不要在构造函数和析构函数中调用虚函数,他们都不是动态绑定的。

2,如果析构函数是虚函数,那么可以看到类似动态绑定的效果,但这并不是动态绑定,也并不意味着我们可以随意在析构函数中调用、间接调用虚函数

3,如果基类存在虚函数,析构函数必须为虚函数,更进一步的讲,如果程序可能会存在 base *p = new drived() 这样的语句,即使没有虚函数也要有虚析构函数

 

4, 为什么会产生这样的效果??

      因为vptr的绑定是发生在对象构造期间的,所以构造函数会有这样一个假设: 我正在构造的对象和我的类型是完全一致的, 同理虚析构函数也会这么假设。所以在构造/析构函数中调用virtual 函数时不会有任何多态效果的。

 

5, 那为什么虚析构函数中调用虚函数会有多态??

      因为析构函数本身如果是虚函数的,在执行虚函数之前会通过vptr找到正确的析构函数,然而在那个析构函数调用的虚函数依旧是没有动态绑定。我们看到的"动态绑定"是虚析构函数的动态绑定,而不是析构函数所调用的虚函数的多态

 

6,考虑下面的代码:

class Base {
public:
    virtual void wtf() { cout << "base" << endl; }
    virtual ~Base() { wtf(); }
};

class Drived : public Base {
public:
    void wtf() { cout << "drived" << endl; }
    ~Drived () { wtf(); }
};


int main() {
    Base *p = new Drived();
    delete p;
    return 0;
}

         输出为 drived  base, 看起来似乎一切正常,但是实际上的运行过程是:由于析构函数动态绑定,所以在delete p时,发现对象实际类型为drived,所以先析构派生类,再析构基类, 所以执行drived::~drived(), 在drived::~drived() 中看见了一个叫wtf()的函数,这个时候不会去查vtab,直接认为它是drived::wtf(),之后执行base::~base()同理。

        但是如果析构函数不是虚函数,那么就只会执行base::~base() 和 base::wtf(),哪怕 wtf是虚函数。更进一步的,凡是代码中发生类似base *p  = new dirved()情况的,几乎都必须要有一个虚析构函数。否则就会出现delete p时只会delete基类部分。。。

        更更更扩展的是,使用 delete [] p 时,为了判断delete的次数和位置, 即使有虚析构函数还是会出问题,因为具体在哪些位置delete 是未定义的。。

        同样的,在函数构造是,会先执行基类构造函数,和基类对应版本的函数,然后是子类的。

 

 

所以不要尝试在  构造/析构 函数中调用虚函数, 这会让程序的意义非常不明确。如果非得调用,显式说明我要调用那个版本的,如base::xx()


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM