联编的概念
静态联编
动态联编
在 C++ 中动态联编需要虚函数的支持,这是因为虚函数的工作原理决定的,而正是因为使用了虚函数来实现动态联编,也让动态联编的效率略低于静态联编。通常,编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员,隐藏成员保存了一个指向函数地址数组的指针 ,这个数组就是虚函数表(virtual function table, vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址,调用虚函数时,程序将查看存储在对象中的 vtbl 地址,然后转向相应的函数地址表,如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。
虚函数这个概念是 C++ 的精华之一,遇到虚函数时要注意以下几点:
1.定义一个函数为虚函数,不代表函数为不被实现的函数(可以有自己的实现)
2.定义它为虚函数是为了允许用基类的指针来调用子类的这个函数(提供了基类调用子类函数的方式)
3.定义一个函数为纯虚函数,代表函数没有被实现(声明后面接=0,例如:virtual func() = 0 此时派生类必须要实现此虚函数)
4.具有纯虚函数的类是抽象类,不能用于生成对象(即不能实例化),只能派生,它派生的类如果没有实现纯虚函数,那么他的派生类还是抽象类。
虚析构函数
虚析构函数顾名思义就是将析构函数定义为虚函数。如果我们在派生中分配了内存空间,但是基类的析构函数不是虚析构函数,就会发生内存泄漏。看下面的例子:
#include <iostream>
using namespace std;
class Base{
public:
virtual void print(){
cout << "This is Base's print function" << endl;
}
/* 对比加与不加 virtual 析构函数的调用情况 */
~Base(){
// virtual ~Base(){
cout << "The destructor of Base" << endl;
}
};
class Derived : public Base{
public:
void print(){
cout << "This is Derived's print function" << endl;
}
~Derived(){
cout << "The destructor of Derived" << endl;
}
};
int main()
{
Base *p = new Derived();
p->print();
delete p;
return 0;
}
不加 virtual 的运行结果:
加上 virtual 的运行结果:
在上面程序示加上 virtual 时编译器还是按照 Base 类型调用了析构函数,没有执行 Derived 类的虚析构函数,就造成了内存泄露。修改 Base 类的析构函数为虚析构函数后实现了多态,就可以确保执行正确的析构函数,完成资源的释放。
总结一下关于虚函数的一些常见问题:
1.虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数,这就是虚函数的基本功能。
2.构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还么有构造好,多态此时是被 disable 的。
3.析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
4.将基类中的一个函数定义为纯虚函数,实际上是将这个类定义位抽象类,不能实例化对象。
5.纯虚函数通常没有定义体,但也可以拥有。(如果 Base 的析构函数为纯虚函数,那么也可以在类外定义 Base::~Base(){…} 的方式来定义其定义体)
6.析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
7.非纯的虚函数必须有定义体,不然是一个错误。
8.派生类的 override 虚函数定义必须和父类完全一致,除了一个特例,如果父类返回值是一个指针或引用,子类 override 时可以返回这个指针(或引用)的派生。如在 Base 中定义了 virtual Base* fun(){ return this; },在 Derive 中可以定义 virtual Derive* fun(){ return this; }。
补充一个具有定义体的纯虚函数的例子
#include <iostream>
using namespace std;
class Base{
public:
virtual void print() = 0;
};
void Base::print(){
cout << "This is Base's fun" << endl;
}
class Derived : public Base{
public:
void print(){
/* 调用基类的纯虚函数 */
Base::print();
cout << "This is Derived fun" << endl;
}
};
int main()
{
Base *T = new Derived();
T->print();
delete T;
return 0;
}
程序输出:
本篇文章参考:阿里云 - C++的静态联编和动态联编