深入理解C++三大特性之一 ——多态
1.多态的实现效果
多态:同样的语句调用能呈现不一样的表现形态;
2.多态实现的三个条件
a)继承
b)virtual关键字修饰
c)父类指针指向或引用子类对象
3.多态的C++实现
通过virtual关键字,告诉C++编译器对这个函数要支持多态;
不要在编译期间根据指针类型判断如何调用;而是要根据运行期间指针所指向的实际对象类型来判断如何调用
4.多态的理论基础
动态联编PK静态联编。根据运行期间实际的对象类型来判断重写函数的调用
5.多态的重要意义
设计模式的基础
6.实现多态的理论基础
函数指针做函数参数
C++中多态的实现原理
1.当类中声明虚函数时,编译器会为此类自动生成并维护一个虚函数表 ——VTABLE
2.VTABLE是一个存储类虚成员函数指针的数据结构,所有virtual成员函数会被编译器放入虚函数表中
3.存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
分析:
void run(Parent* p)
{
p->talk();
}
if run不是virtual关键字修饰的虚函数,则很简单,根据静态联编,由Parent类型决定调用的函数。
else run是virtual 函数,需要动态联编。编译器需要根据传入的对象类型,来调用其相应的vptr指针,然后vptr指针分别指向不同的VTABLE,而基类的VATBLE 与派生类的VATBLE 里面存储的函数地址是不同的。
C++ 多态的实现机制的缺点
1.从效率上讲,使用虚函数告知编译器需要动态联编必然导致效率降低。因为普通函数的调用是编译器在编译期间就能确定 的,而多态需要延迟到运行期间才能根据传入的对象类型来得到被调用函数的地址。
2.由于需要为每个类维护一张VTABLE 及为每个对象自动生成一个vptr指针,空间效率降低。特别是某些基类的virtual函数特别多的时候,当被继承而又不需要重写其功能时,造成很大浪费但又不得不维护这张庞大的VATBLE。
面试题1:有必要将所有函数都声明为虚函数吗?
通过虚函数表指针vptr调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多,所以没有必要
如何证明vptr指针的存在?
思路:利用sizeof计算类的大小,加上virtual后,大小增加4。(注:默认空类的大小是1,原因是?见effective系列文章)
vptr指针初始化时机
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class Person
{
public:
Person()
{
cout << "Person---构造函数" << endl;
talk();
}
virtual void talk()
{
cout << "Person---说人类的语言" << endl;
}
};
class Chinese :public Person
{
public:
Chinese()
{
cout << "Chinese---构造函数" << endl;
}
virtual void talk()
{
cout << "Chinese---说汉语" << endl;
}
};
void main()
{
Chinese xm;
cin.get();
}
`《Inside the c++ Object model》`中指出vptr初始化的时间为:
After invocation of the base class constructors but before execution of user-provided code or the expansion of members initialized within the member initialization list.
意思是在所有基类构造函数之后,但又在自身构造函数或初始化列表之前。
实际上,还可以看出子类对象的vptr是被初始化了两次: 1.先在基类的构造函数前初始化为指向基类虚函数表(vtable)的指针 2.然后在自身构造函数前初始化为指向自身类vtable的指针
注:vptr初始化是在初始化列表之前还是之后是跟编译器实现有关的,在VC++6.0编译器中是在初始化列表之后。但其肯定是在基类的构造函数之后,且在自身的构造函数之前。
多态之所以重要与强大,是在于其可以接口重用,早期设计的框架可以在不变前提下使用后期实现的代码来呈现不同的形态或效果。
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class Person
{
public:
Person()
{
cout << "Person---构造函数" << endl;
talk();
}
virtual void talk()
{
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"Person---说人类的语言"</span> << endl;
}
};
class Chinese :public Person
{
public:
Chinese()
{
cout << “Chinese—构造函数” << endl;
}
<span class="hljs-keyword">virtual</span> <span class="hljs-keyword">void</span> talk()
{
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"Chinese---说汉语"</span> << endl;
}
};
class American :public Person
{
public:
American()
{
cout << “American—构造函数” << endl;
}
<span class="hljs-keyword">virtual</span> <span class="hljs-keyword">void</span> talk()
{
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"American---说美式英语"</span> << endl;
}
};
talkDifferentLanguage**早期**设计的接口,以后都不需要更改,却可以适应**后期**不同的实现代码 ------------------------------------------------------------
void talkDifferentLanguage(Person* p) { p->talk(); }
void main() { Chinese xm; talkDifferentLanguage(&xm); American john; talkDifferentLanguage(&john);
cin.get(); }