C++虚函数与动态联编

来源:互联网 发布:单片机温度报警器程序 编辑:程序博客网 时间:2024/05/23 21:29

静态联编和动态联编

编译器将源代码中的函数调用解释为执行特定的函数代码块,这一行为被称为函数名联编(binding)。联编行为发生在编译过程中则被称为静态联编(static binding),又称为早期联编(early binding)。然而,虚函数使这项工作变得更困难。虚函数的特点使得程序使用哪一个函数是不能再编译时确定的,因为编译器不知道用户将选择哪一种类型的对象。所以,编译器必须生成能够在程序运行时选择正确的虚函数的代码,这被称为动态联编(dynamic binding),又称为晚期联编(late binding)。


虚函数

定义

虚函数是实现C++多态性的重要一环。在C++继承中,如果一个基类的派生类需要重载基类的成员函数,在基类中将这个预期在派生类中要重载的成员函数定义为虚函数,就可以做到这一点。

#include<iostream>class A{public:    virtual void print (void){std::cout << "This is A\n";};    A (){};    virtual ~A (){};};class a : public A{public:    void print (void){std::cout << "This is a\n";};    a (){};    virtual ~a (){};};int main (void){    A *p = new A;    p->print();    p = new a;    p->print();    delete p;    return 0;}


如果不将print函数定义为虚函数,则会是另一种结果。


原因是如果通过引用或指针调用成员函数,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型来选择方法;如果使用了关键字virtual,程序将根据引用或指针指向的对象的类型来选择方法。


虚函数特点

·虚函数只需要在声明中使用virtual关键字,在具体定义中不用。

·在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的。

·如果使用指向对象的引用或指针来调用虚函数,程序将使用类对象类型定义的方法,而不适用为引用或指针类型定义的方法,这称为动态联编或晚期联编,这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。

·如果定义的类将被作用于基类,则应将那些要在派生类中重新定义的类方法声明为虚的。

·构造函数不能是虚函数。因为创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制,因此派生类不继承基类的构造函数,所以讲类构造函数声明为虚的没什么意义。

·析构函数应当是虚函数,除非类不用做基类。因为如果不将析构函数设为虚的,静态联编将调用基类的析构函数,也即释放了派生类中从基类继承下来的那一部分内存,而派生类独有的内存没有被释放;如果是虚析构函数,则动态联编将按与实例化相反的顺序释放派生类的内存再释放基类内存。所以通常应给基类提供一个虚析构函数,即使它并不需要析构函数。

·友元不能是虚函数,因为友元不是类成员,而只有类成员才能是虚函数。如果由于这个原因引起了设计问题,可以通过让友元函数使用虚析构函数来解决。

·如果派生类没有重新定义虚函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外的情况是基类版本是隐藏的。

·如果对虚函数重新定义将隐藏虚函数。

虚函数工作原理

通常编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function tablevtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有的虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该btbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中。注意,无论类中包含的虚函数是1个还是10个,都只需要在对象中添加一个地址成员,只是表的大小不同而已。

调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。如果类声明中定义的第一个虚函数,则函数将使用数组中的第一个函数地址,并执行具有该地址的函数。如果使用类声明中的第三个虚函数,程序将使用地址为数组中的第三个元素的函数。

从虚函数的工作原理看出,使用虚函数时,增加了一定的时间和空间成本,主要包括:

·每个对象都将增大,增大量为存储地址的空间;

·对于每个类,编译器都创建一个虚函数地址表(数组);

·对于每个函数的调用,都需要执行一项额外的操作,及到vtbl中查找地址。

因此,C++将函数的默认联编方式设为静态联编是正确的,只有在需要相同函数在其派生类中的实现方法不同的时候声明为虚函数,使用动态联编才是比较合适的选择。




本文部分内容摘自《C++ Primer Plus(中文版)第6版》