在构造析构函数中调用虚函数时,总是无法呈现多态。
例:
#include <iostream>
using std::cout;
using std::endl;
namespace test{
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
Base(){cout<<"基类构造!\n";
f();}
~Base()
{
cout<<"基类析构!\n";
f();
}
};
class Derived:public Base
{
public:
virtual void f()override{cout << "Derived::f" << endl;}
Derived()
{
cout<<"派生类构造!\n";
f();
}
~Derived()
{
cout<<"派生类析构!\n";
f();
}
};}
int main()
{
using namespace test;
Derived *m_test= new Derived;
cout<<"==============\n";
delete m_test;
}
可以看到,当构造派生类对象时,基类的构造函数是调用了基类中的虚函数。
当析构时,基类的析构函数也是调用基类中的虚函数。
网上很多解释说:
当构造派生类对象时,先调用基类的构造函数,此时派生类还没有被构造出来,所以调用的是基类的虚函数。
而析构时,派生类已经析构掉了,所以基类析构时仍调用的是基类的虚函数。
错!
实际上,无论派生类有没有被构造出来,还是已经析构了。在构造、析构函数中一定只会调用本类中的虚函数。
还是刚才的代码,把main函数改为
using namespace test;
Base *m_test= new Derived;
cout<<"==============\n";
delete m_test;
显然,此时m_test没有调用派生类的析构函数,但是在基类析构时仍是调用基类的虚函数
因为在函数进入构造、析构函数时,一定会把虚指针填充为当前类虚表的首地址
#include <iostream>
#include<iomanip>
using std::cout;
using std::endl;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
~Base()
{
void* tmp= (void*)(this);
size_t* tmp2=(size_t*)(tmp);
cout<<"此时虚表首地址为"<<*tmp2<<endl;
cout<<"基类析构!\n";
f();
}
};
class Derived:public Base
{
public:
virtual void f()override{cout << "Derived::f" << endl;}
~Derived(){cout<<"派生类析构!\n";f();}
};
int main()
{
Derived* tmp_derived=new Derived();
size_t* vptr_derived=(size_t*)(tmp_derived);
cout<<std::hex<<"派生类的虚表首地址为:"<<*vptr_derived<<" "<<std::dec<<*vptr_derived<<endl;
Base* tmp_base=new Base();
size_t* vptr_base=(size_t*)(tmp_base);
cout<<std::hex<<"基类的虚表首地址为:"<<*vptr_base<<" "<<std::dec<<*vptr_base<<endl;
cout<<"==========================\n";
//强行向基类对象中加入指向派生类虚表的指针
*vptr_base=*vptr_derived;
tmp_base->f();//虚指针修改成功!
cout<<"===修改基类对象的虚指针===\n";
tmp_base->~Base();
cout<<"=======================\n\n";
}