01
—
C++纯虚函数探索
C++的运行时多态由虚函数或纯虚函数实现,纯虚函数没有函数定义,在声明的时候要在函数名后面加"= 0",拥有纯虚函数的类我们称为抽象类。从我们知道,带有虚函数的类内部会有一个虚函数表指针vfptr,同理带有纯虚函数的类内部也有一个虚函数表指针vfptr,但是虚函数和纯虚函数之间又有一些不一样,先看下面这段代码。
#include <iostream>
class Base{
public:
virtual void fun1() = 0;
virtual void fun2() = 0;
};
int main()
{
Base* b = new Base();
return 0;
}
编译信息如下:
class Base{
public:
virtual void fun1() = 0;
virtual void fun2() = 0;
void fun3(){
std::cout << "Base::fun3 run" << std::endl;
}
virtual ~Base() = default;
};
编译运行依然出错!
#include <iostream>
class Base{
public:
virtual void fun1() = 0;
virtual void fun2() = 0;
void fun3(){
std::cout << "Base::fun3 run" << std::endl;
}
virtual ~Base(){
std::cout << "Base::desconstructor run" << std::endl;
};
};
class Derive : public Base{
public:
void fun1(){
std::cout << "Derive::fun1 run" << std::endl;
}
void fun2(){
std::cout << "Derive::fun2 run" << std::endl;
}
~Derive(){
std::cout << "Derive::desconstructor run" << std::endl;
}
};
int main()
{
Base* b = new Derive();
b->fun1();
b->fun2();
delete b;
return 0;
}
编译运行输出如下:Derive::fun1 run
Derive::fun2 run
Derive::desconstructor run
Base::desconstructor run
和虚函数一样,基类指针可以保存派生类实例地址,可以通过基类指针调用派生类实现函数。看到这里眼尖的人就会发现,抽象类的析构函数是虚析构函数,如果析构函数不是虚析构函数呢?运行会出现什么问题?下面这段代码会演示非虚析构函数会发生什么。
class Base{
public:
virtual void fun1() = 0;
virtual void fun2() = 0;
void fun3(){
std::cout << "Base::fun3 run" << std::endl;
}
~Base(){
std::cout << "Base::desconstructor run" << std::endl;
};
};
编译运行输出如下:Derive::fun1 run
Derive::fun2 run
22:42:50: 程序异常结束。
程序在析构时发生了异常!这是因为delete使用的是基类指针而不是派生类的指针,那么我们现在将主函数改成如下:
int main()
{
Derive* d = new Derive();
d->fun1();
d->fun2();
delete d;
return 0;
}
编译输出如下:
Derive::fun1 run
Derive::fun2 run
Derive::desconstructor run
Base::desconstructor run
现在输出正常了。之前到底是怎么回事呢?这是因为在调用delete时使用的是基类指针,且基类是抽象类,如果基类析构函数不是虚析构函数,在释放内存时找不到派生类的析构函数,因此抽象类的析构函数必须是虚析构函数才能通过基类指针完全释放基类和派生类内存。通过基类调用派生类函数过程:通过虚函数表指针vfptr找到虚函数表vftable,再从虚函数表vftable找到派生类函数地址,进而调用实际的派生类函数。有关虚函数表和虚函数表指针可以查看。往期推荐