首页 > 代码库 > 浅谈为什么只有指针能够完成多态及动态转型的一个误区

浅谈为什么只有指针能够完成多态及动态转型的一个误区

c++多态由一个函数地址数组Vtable和一个指向Vtable的指针vptr实现。

 

具体来说,类拥有自己的vtable,类的vtable在编译时刻完成。

每个对象有自己的vptr指针,该指针初始化时指向对象所实现的类的vtable。

 

关于向上转型的误区:

  通常对于向上转型的理解是这样的,当子类对象向上转型(允许隐式)成父类对象时,实际上只是将子类对象暂时看做父类对象,内部的数据并未改变。

  对于没有虚函数的对象,这句话是正确的,但是,当引入虚函数后,这样的理解是有问题的,实际上,向上转型的过程中,vptr指针的指向随着转型改变,这一过程是upcast机制定义好的,也就是说,在任何时刻需要将子类看成父类时,vptr就动态的改变指向,Vprt指针总是指向对象的动态类型。

 

推广之,任何的类型转化都导致vptr指向的改变,指向所转型的类的vtable,如果对应类没有vtable,则动态删除vptr。

 

下面解释为什么只有指针和引用才具有多态性,测试如下:

#include <iostream>using namespace std;class A{    virtual void func(){        cout<<"A‘s func is called"<<endl;    }};class B:public A{    virtual void func(){        cout<<"B‘s func is called"<<endl;    }};int main(){    A a;    B b;    cout<<"Vtable address of A is"<<*(void**)&a<<endl;    cout<<"Vtable address of B is"<<*(void**)&b<<endl;    A tmp=b;    cout<<"after upcast and copy:  "<<*(void**)&tmp<<endl;    A* p=&b;    cout<<"after upcast and reference:  "<<*(void**)&(*p)<<endl;}

测试结果如下:

Vtable address of A is0x1000020e0

Vtable address of B is0x100002110

after upcast and copy:  0x1000020e0

after upcast and reference:  0x100002110

可见,确实只有指针才实现了多态。

 

原因如下:

A tmp=b;

当向父类引用拷贝一个子类对象时,这时实际发生的是,子类对象先向上转型成父类对象,然后在进行赋值运算,由于未定义=的运算符重载,所以发生的一定是bitwise copy,因此,tmp和b的vptr指向不一致,一定是b在发生隐式的向上转型时发生了改变。

 

A* p=&b;

同样,该过程包含一个向上转型和一个拷贝赋值,不同的是,向上转型和拷贝都是地址,首先,b的地址(B*)向上转型成(A*)类,然后将该地址的副本交给p,与值拷贝不同的是,A*和B*根本就没有vptr,也就不存在vptr指向改变的问题了。

通过这种方式,我们使用一个父类的引用,但vptr并未改变,依旧指向了对象的动态类型,多态也得以实现。

 

ps,在函数传参过程中的多态也是一样的。

对于这样一个函数

void fun(A a);  将B b传入时,首先发生了b的向上转型,然后发生了值拷贝,vptr发生了改变,多态失败。

void fun(A* a)  将B* b传入时,发生b的向上转型和拷贝,但是地址变量的转型和拷贝不改变vptr的指向,vptr指向得以保留,多态成功。