对于C++中多态性是面向对象中很重要的特性之一,每种面向对象的语言对于多态的实现都不尽相同。
最近,学习中
发现一段既陌生又熟悉的代码,大致情况如下#include
#include <iostream>
using namespace std;
class BaseClass
{
public:
virtual void Display()
{
cout<<"BaseClass::Display()"<<endl;
}
virtual void Display(int a)
{
cout<<"BaseClass::Display(int a)"<<endl;
}
};
class ExtendClass:public BaseClass
{
public:
virtual void Display()
{
cout<<"ExtendClass::Display()"<<endl;
}
virtual void Display(int a)
{
cout<<"ExtendClass::Display(int a)"<<endl;
}
virtual void Display(float a)
{
cout<<"ExtendClass::Display(float)"<<endl;
}
};
int main()
{
BaseClass* ptrBaseClass = new BaseClass();
ptrBaseClass->Display();
ptrBaseClass->Display(1);
ptrBaseClass->Display(1.1);
return 0;
}
代码的核心思想是
1.基类实现了多个虚函数
2.子类继承基类并且override所有虚函数
3.作者发现子类应该“
重载一下”以满足需求,这样的需求原因可能很多
代码即熟悉是因为,overload(重载)的需求在
实际应用中很多,因此不感到奇怪。
陌生的原因是,这种写法感觉很奇怪,没见过(因为是
错误的,所以没人这么写)
输出结果为
BaseClass::Display()
BaseClass::Display(int a)
BaseClass::Display(int a)
并且在编译是编译器发出强制将float转换为int的警告
首先效果肯定不是原有作者要的结果,然后我开始分析原因
根据Thinking in C++中对于vptr和vtable的解释,貌似我无法找到与这种现象有关的详细解释,也许是我没有发现。
随后我便开始自己解释:
既然所有的虚函数都存放在vtable中,那么指针通过vptr查找到vtable中虚函数时,应该也能找到“float”版的函数。但是他为什么就会去调用int
版本呢?通过以往经验我查看了反汇编
发现调用int和调用float部分的汇编代码一模一样,我开始认为这一切的原因出在了编译过程中。可是我始终无法从vptr和vtable的角度出发进行解释这种现象。
最后在请教了高人之后,知道了一件很重要但始终被我
误解的事(都是以前教条主义惹得祸,当时学的时候就应该追根溯源)。
当诸如ptrBaseClass->Display();话出现时,编译器是如何解释的呢?
1.因为指针是基类的,因此他将查看基类的所有函数,查看是否满足要求的。
2.如果查看到且查看到的函数发现是virtual的,则在翻译成汇编时翻译成:请到vtable中偏移地址为XXX的地方寻找代码。
3.如果不是virtual,则直接把基类的代码地址获得
4.如果没有在基类的函数中查找到相同的函数,则报出找不到的错误
发现没有,其实多态在C++中的实现还是根据基类的指针来查看函数是否合法。
做个实验,如果你在子类中新增一个函数,然后通过基类进行调用,发现该函数不存在,编译错误。
因此在编译时,函数具体代码区的查找是根据基类指针来的。
当遇到多态时,无非就是告诉调用者,请到具体对象实例中vptr所指向的vtable中所指向的地址去寻找代码,这个过程是运行时完成的。
综上所述,对于我个人而言更加明确了一点,不要在多态的时候去想重载,这是不可能的。