c++编译器对多态的实现原理总结

  

结果是1,因为空类型的实例不包含任何信息,按道理sizeof计算之后结果是0,但是在声明任何类型的实例的时候,必须在内存占有一定的空间,否则无法使用这些实例,至于占据多少内存大小,由编译器决定。

还是1,因为我们调用构造函数和析构函数,只需要知道函数的地址即可,而这些函数的地址只和类型相关,和类型的实例无关,编译器不会为这两个函数在实例内添加任何额外的信息。

c++编译器发现了类型里有虚函数,,就会为这个类型生成一个虚函数的表,并在该类型的每一个实例中添加一个指向虚函数表的指针,在32位机器,指针类型大小是4字节,结果是4、64位机器中,指针大小是8字节,结果是8 .

多态:同样的调用语句有多种不同的表现形态

<强>看下面的代码例子:

 class 动物
  {公众:,,,,void 睡眠()
  ,,,{
  ,,,,,,,cout<& lt;“animal  sleep" & lt; & lt; endl;
  ,,,},,,,void 呼吸()
  ,,,{
  ,,,,,,,cout<& lt;“animal  breathe" & lt; & lt; endl;
  ,,,}
  },class 鱼:public 动物
  {公众:,,,,void 呼吸()
  ,,,{
  ,,,,,,,cout<& lt;“fish  bubble" & lt; & lt; endl;
  ,,,}
  },int 主要(空白)
  {
  ,,,fish 跳频;
  ,,,animal  *潘=和跳频;
  ,,,潘→呼吸(),,,,,return  0;
  }

父类指针指向了子类对象,调用了 breathe 方法,那么结果是animal breathe,也就是说调用的是父类的breathe方法。 这没有实现多态性。因为C++编译器在编译的时候,要确定每个对象调用的函数的地址,这称为早期绑定(early binding),当fish类的对象fh的地址赋给父类的pAn指针时,C++编译器进行了类型转换,它认为父类的指针变量pAn保存的就是animal对象的地址。当在main()函数中执行pAn->breathe()时,调用的就是animal对象的breathe函数。

进一步说:

c++编译器对多态的实现原理总结

在我们构造fish类的对象时,首先要调用父类:animal类的构造函数去构造animal类的对象,然后才调用fish类的构造函数完成自身部分的构造,从而拼接出一个完整的fish对象。当将fish类的对象转换为animal类型时,该对象就被认为是原对象整个内存模型的上半部分,也就是图中的“animal的对象所占内存”。

那么当利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的方法。因此,输出animal breathe。这不是多态的表现形式。

必要的前提是必须有继承关系、然后我们需要父类指针(引用)去调用子类的对象,且关键是:子类有对父类的虚函数的重写。virtual关键字,告诉编译器这个函数要支持多态,我们不要根据指针类型判断如何调用方法,而是要根据指针所指向的实际对象类型来判断如何调用。

前面的例子,输出的结果是因为编译器在编译的时候,就已经确定了对象调用的函数的地址,要解决这个问题就要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字,这样的函数我们称为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。

所谓的动态联编:根据实际的对象类型来判断重写函数的调用。

当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)

c++编译器对多态的实现原理总结

 

如图,编译器为每个类的对象提供一个虚表指针vptr,这个指针指向对象所属类的虚函数表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。

c++编译器对多态的实现原理总结

在上例子中:

    fish fh;
  ,,,animal  *潘=和跳频;
  ,,,潘→呼吸();

c++编译器对多态的实现原理总结