通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组(虚函数表)的指针,虚函数表中存储了为类对象进行声明的虚函数的地址,例如:
class Scientist {
...
char name[40];
public:
virtual void show_name();
virtual void show_all();
};
class Physicist : public Scientist
{
...
char field[40];
public:
void show_all(); //重写父类方法
virtual void show_field();
...
};
类
Scientist
有一个隐藏的数据成员,里面存储了一个指向一张虚函数表的指针,该虚函数表记录了类
Scientist
定义的虚函数的地址;同理类
Physicist
也同样有一个隐藏的数据成员,里面存储了一个指向一张虚函数表的指针,该虚函数表记录了类
Physicist
继承自
Scientist
的虚函数的地址和自己新定义虚函数的地址。
调用虚函数时,程序将查看存储在对象中的虚函数表地址,然后通过查询虚函数表转向相应的虚函数,所以虚函数相比于非虚函数存在一些缺点(凡是都具有两面性):
- 每个对象都将增大,增大量为存储地址的空间;
- 对于每个类,编译器都创建一个虚函数地址表(数组);
- 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址
虽然非虚函数的效率比虚函数稍高,但不具备动态联编功能。
虚函数注意事项:
- 在基类方法的声明中使用关键字
可使该方法在基类以及所有的派生类(包括派生类派生出来的类)中是虚的;virtual
- 如果使用指向对象的引用或指针来调用虚方法,程序将使用对象类型定义的方法,而不使用为引用或指针类型定义的方法,即非虚看引用或指针类型,虚看对象类型;
- 如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的;
- 构造函数不能是虚函数;
- 析构函数应当是虚函数,除非类不用做基类
class Singer : public Employee
{
...
}
Employee * pe = new Singer;
delete pe;
如果使用静态联编 ,
delete pe;
将根据指针类型调用类
Employee
的析构函数
~Employee()
,这样只会释放类
Singer
从类
Employee
中继承过来的那一部分,自身的那一部分因为没有调用自身的析构函数而不会被释放;
如果使用动态联编,
delete pe;
将根据对象类型先调用类
Singer
的析构函数
~Singer()
,释放属于类
Singer
的那一部分,然后再调用类
Employee
的析构函数
~Employee()
,释放属于类
Employee
的那一部分;
- 友元函数不能是虚函数,因为友元函数不是类成员,而只有成员才能是虚函数,如果因为这个原因引起的设计问题,可以通过让友元函数使用虚成员函数来解决;
- 如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外的情况是基类版本是隐藏的;
- 重新定义将隐藏方法
class Dweling
{
public:
virtual void showperks(int a) const;
...
};
class Hovel : public Dwelling
{
public:
virtual void showperks() const;
}
Hovel trump;
trump.showperks(); //valid
trump.showperks(5); //invalid
新定义将
showperks()
定义为一个不接受任何参数的函数,重新定义不会生成函数的两个重载版本,而是隐藏了接受一个int参数的基类版本。
- 如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(注意:这种例外只适应于返回值,而不适用于参数)
class Dweling
{
public:
virtual Dweling & showperks(int a) const;
...
};
class Hovel : public Dwelling
{
public:
virtual Hovel & showperks(int a) const;
}
- 如果基类声明被重载了,则应在派生类中重新定义所有的基类版本
class Dweling
{
public:
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
...
};
class Hovel : public Dwelling
{
public:
virtual void showperks(int a) const;
virtual void showperks(double x) const;
virtual void showperks() const;
}