虚调用是相对于实调用而言,它的本质是动态联编。在发生函数调用的时候,如果函数的入口地址是在编译阶段静态确定的,就是是实调用。反之,如果函数的入口地址要在运行时通过查询虚函数表的方式获得,就是虚调用。
虚调用不能简单的理解成“对虚函数的调用”,因为对虚函数的调用很有可能是实调用。考察如下程序。
#include <iostream>
using namespace std;
class A{
public:
virtual void show(){
cout<<"in A"<<endl;
}
};
class B:public A{
public:
void show(){
cout<<"in B"<<endl;
}
};
void func(A a){
a.show();
}
int main(){
B b;
func(b);
}
程序运行输出结果是:in A。在函数func()中,虽然在class A中函数show()被定义为虚函数,但是由于a是类A的一个示例,而不是指向类A对象的指针或者引用,所以函数调用a.show()是实调用,函数的入口地址是在编译阶段静态决定的。
函数调用func(b)的执行过程是这样的:先由对象b通过类A的赋值构造函数,产生一个类A的对象作为函数func()的实参进入函数体。在函数体内,a是一个“纯粹”的类A的对象,与类型B毫无关系,所以a.show()是实调用。
在构造函数和析构函数中调用虚函数,对虚函数的调用实际航是实调用。这是虚函数被“实调用”的另一个例子。由于从概念上说,在一个对象的构造函数运行完毕之前,这个对象还没有完全诞生,所以在构造函数中调用虚函数,实际上都是实调用。
析构时,在销毁一个对象时,先调用该类所属类的析构函数,然后再调用其基类的析构函数。所以,在调用基类的析构函数时,派生类已经被析构了,派生类数据成员已经失效,无法动态的调用派生类的虚函数。
考察如下例子。
#include <iostream>
using namespace std;
class A{
public:
virtual void show(){
cout<<"in A"<<endl;
}
A(){show();}
~A(){show();}
};
class B:public A{
public:
void show(){
cout<<"in B"<<endl;
}
};
int main(){
A a;
B* pb=new B();
cout<<"after new"<<endl;
delete pb;
cout<<"after delete"<<endl;
}
程序的执行结果是: in A in A after new in A after delete in A
在构造类B的对象b时,会先调用基类A的构造函数,如果在构造函数中对show()的调用是虚调用,那么应该打印出“in B”。析构也是如此,对虚函数的调用是实调用。因此,一般情况下,应该避免在构造函数和析构函数中调用虚函数,如果一定要这样做,程序猿必须清楚,这是对虚函数的调用其实是实调用。
设立虚函数的初衷就是想在设计基类的时候,对该基类的派生类实施一定程度的控制。笼统的说,就是“通过基类访问派生类成员”。 因此,虚调用最常见的形式是:通过指向基类的指针或引用来访问派生类对象的虚函数。这种情况较为常见。
不常见形式: 不过由于虚调用是通过查询虚函数表来实现的,而拥有虚函数的对象都可以访问道所属类的虚函数表,所以,一个不常见的做法是通过指向派生类对象的指针或引用调用基类对象的虚函数,考察如下代码。
#include <iostream>
using namespace std;
class A{
public:
virtual void show(){
cout<<"in A"<<endl;
}
};
class B:public A{
public:
void show(){
cout<<"in B"<<endl;
}
};
int main(){
A a;
B& br=static_cast<B&>(a);
br.show();
}
程序输出结果是:in A。通过派生类对象的引用rB实现了对基类中虚函数show()的调用,如果在class A的定义中,将函数show()前面的关键字virtual去掉,那么程序的执行结果就是in B,br.show()就变成了实调用。
答案是否定的。在实际应用中,绝大多数的虚调用的确是显示借助于指针或者引用来实现,但是可以通过间接的方式来实现虚调用。
#include <iostream>
using namespace std;
class A{
public:
virtual void show(){
cout<<"in A"<<endl;
}
void callfunc(){show();}
};
class B:public A{
public:
void show(){
cout<<"in B"<<endl;
}
};
int main(){
B b;
b.callfunc();
}
程序的执行结果是:in B。在这个程序中,看不到一个指针或者引用,却发生了虚调用。函数调用b.callfunc()执行的实际上是A::func(),如果在class A中却掉函数show()前面的关键字virtual,那么程序的输出结果是:in A。也就是说,在函数callfunc()中,函数调用show()是一个虚调用,它是在运行时才决定使用派生类中的虚函数还是实用基类中的虚函数。
[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[8.6(P296-P299)]