c++ 多态 运行时多态和编译时多态_【C/C++】深度剖析 多态(polymorphism)的底层实现机制...

c++ 多态 运行时多态和编译时多态_【C/C++】深度剖析 多态(polymorphism)的底层实现机制..._第1张图片

1 什么是多态?

多态(Polymorphism)源自于希腊语,意思是“多种形状”。在C++中,允许通过基类型的指针或引用去访问派生对象中的函数,并允许需要执行的函数在运行时进行延迟绑定(Late binding),这称之为多态。多态的前提条件是继承。

另外, 对于重载(overload)的实现也可称之为多态,只不过发生在静态编译阶段,根据函数参数类型的区别就确定了应该调用的函数。

本文主要介绍通过覆盖(override)机制来实现动态绑定的多态

2 为什么要用多态?

1)多态意味着可以用同一函数名去执行不同的动作,对函数命名复用的同时,简化了代码。

2)约定了实现的接口,便于子类的具体功能实现的同时,接口不变。

3)各个子类相互不影响,提高代码的扩展性。

4)便于后期维护,如果不是多态,就需要手动判断具体需要执行的方法(一堆if…else…)。

3 如何使用多态机制?

在C++中,多态的实现是通过覆盖(override),而决定是否覆盖函数的关键点在于该基类中的函数是否有关键字virtual的修饰,被修饰的函数被称为虚函数。

所以在基类中 通过virtual修饰的成员函数即可被派生类中定义的同名函数覆盖(override)。

那么问题的关键来了,C++究竟是如何实现override的?override了谁?跟编译器有何关系?

为什么基类的指针可以访问派生对象中的函数?

4 实现多态机制的底层原理

基类中含有virtual 修饰的成员函数,编译器将在内存模型中的添加虚函数表的指针(vptr),其占用sizeof(void *)大小(跟平台相关)。该vptr指向存储在别处的虚函数表(vtbl),vtbl中又存放着类中的虚拟成员函数的地址。

现编码进行试验,通过打印地址的方式来分析内存布局,以及覆盖的机制,和多态的原理。

4.1源码:

#include 

详细源码分析见注释(主要是指针的应用)

4.2 g++编译,执行,查看输出:(平台:ubuntu16.04,gcc5.4)

c++ 多态 运行时多态和编译时多态_【C/C++】深度剖析 多态(polymorphism)的底层实现机制..._第2张图片

4.3 内存分配模型:(关于内存对齐,可以参考内存对齐 到底怎么回事?

c++ 多态 运行时多态和编译时多态_【C/C++】深度剖析 多态(polymorphism)的底层实现机制..._第3张图片

4.4 试验结论

1) 同一基类的不同派生类对象,其共同拥有vptr和vtbl。因为 *vp 和 *vp1 的指向一致(0x401568),且虚函数表中的函数地址也一样。也就是不同的对象之间的虚函数表是相同的。

2) 同一基类的不同派生类对象,其成员变量的地址不一样,可以理解为各自copy了一份。

3) 基类A的指针可以访问派生类B的对象中的成员函数,实现了多态(输出 B class bf : 20)。

实现多态的原理,在于类B对象中的虚函数表中的bf 覆盖了继承的bf。当调用a3->bf()时,vptr实际指向的虚函数表中的第一项是 B类对象中的bf函数。

原本bf地址:0x401234 ; 覆盖后:0x4012ea。改变了函数地址,也就改变了调用时执行的函数。这就是override,覆盖的是虚函数表中的函数地址,也就是多态的底层实现。

4) 基类A指针不能访问基类A中的不存在成员,虽然可以通过函数指针调用派生对象B中的自有的虚函数(这显然是不安全的,但是从理论上来说,通过拿到地址后,指针可以访问一切。。。),但是函数中的变量是不存在的,显然是随机值,比如结果中的18549888

5) 关于安全性。通过函数指针,可以访问到派生类中的自有虚拟函数,以及private修饰的virtual函数,这是很危险的。虽然直接访问时,静态编译会编译报错,但是动态运行时却可以通过指针进行访问。(指针是双刃剑,即可提高效率,也可让程序崩溃…)

你可能感兴趣的:(c++,多态,运行时多态和编译时多态,通过指针实现冒泡排序的函数)