首页 > 代码库 > 玩坏C++的多态机制

玩坏C++的多态机制

      面向对象的三个特点,封装继承多态,好了,当面向对象遇上指针,就有了C++。三个特点中的封装继承很容易理解,按笔者的个人理解,封装继承是为多态而生。当一个父类有多个继承类时,通过对父指针赋予不同继承类的对象,就可以灵活地调用继承类中的继承函数了,这就是多态。

      虚函数是实现多态的重要元素,请看:

class A{public:    void a0(){cout <<"a0"<<endl;}    virtual void a1(){cout <<"a1"<<endl;}};class B: public A{public:    void a0(){cout <<"b.a0"<<endl;};    void a1(){cout <<"b.a1"<<endl;};};main(){    A * a0 = new B ();    a0->a0();    a0->a1();    A a1 = B();    a1.a0();    a1.a1();
delete a;
}

 

    输出是a0, b.a1, a0, a1。喻示了两点:其一,C++多态是通过指针来实现的,这和Java中通过类型转换(C#中称为装箱和拆箱)不同,因为执行A a1=B()时,首先调用了B de 构造函数构造出B对象,然后调用A的复制构造函数构造A,因此,最终调用的是A的复制构造函数,在调用函数时当然也调用A的函数了;其二,virtual的功能是使用多态时,子类的同名同参数的函数得以覆盖父类函数,而对于非虚函数,C++中在通过对象调用成员函数时,函数的入口在编译时就静态地确定了,而编译器是不在乎指针在赋值后会指向什么对象的。

    这一切来自于C++的虚函数表机制。虚函数表是一个连续的内存空间,保存着一系列虚函数指针。在构造一个子对象时,内存空间最开始的4B保存一个虚函数表的入口地址。如上例中,A的虚函数表为<A::a1>,B继承A并重写了虚函数a1,因此B的虚函数表为<B::a1>,即在继承的时候,用B::a1的函数地址覆盖了A::a1的地址。于是有了下面的代码:

class A{public:    void a0(){cout <<"a0"<<endl;}    virtual void a1(){cout <<"a1"<<endl;}    virtual void a2(){cout <<"a2"<<endl;}};class B: public A{public:    void a0(){cout <<"b.a0"<<endl;};    void a1(){cout <<"b.a1"<<endl;};    void a2(){cout <<"b.a2"<<endl;}};type void ( * Function)();main(){    A * a = new B ();    Function p =  (void (*)(void))*( (int *) *(int*)(a) + 0 );    p();    delete a;}

其中:

  a是对象的地址,

  (int *) a是对象最开始4字节的地址

  * (int *)a是对象的最开始的4字节

  (int *) * (int *)a是对象最开始的四个字节,实际上是个地址,是什么地址呢,是虚函数表存放的地址

  * (int *) * (int *)a是虚函数表的第一项,也就是第一个虚函数的地址,因此+0表示第一个函数,+1表示第二个函数,以此类推

  于是通过函数指针p,成功地访问了B的第一个虚继承函数a1。虚函数表就是这么一回事。下面两个小魔术来了。

 

1,通过函数指针访问子类的私有函数

2,通过函数指针访问父类的私有函数

class A{    virtual void a0()    {        printf("A::a0 (private)\n");    }public:    explicit A(){}    virtual void a1()    {        printf("A::a1 (public)\n");    }};class B : public A{public:    explicit B(){}private:    int y;    virtual void a1()    {        printf("B::a1 (private)\n");    }};typedef void (* Function)();main(){    A * a = new B();    Function p;    p = ( void (*)(void) ) *( (int *)  *(int*)(a) + 0 );    p();    p = (Function) *( (int*) *(int*)(a) + 1 );    p();
a->a1(); delete a;}

    其中A的虚函数表是<private A::a0, public A::a1>, B的虚函数表是<private A::a0, private B::a1>。

    在第一次调用时p指向private A::a0,而第二次调用时p指向private B::a1。权限的检查是在编译阶段,因此动态的指针调用绕过了权限检查。

    在第三次调用时(a->a1())时,由于对权限的检查是在编译阶段,而编译器不检查a到底指向什么对象(因为这是动态的),只看a的类型。编译器发现a是A的指针,而a1()在类A中是public函数,因此权限检查顺利地pass。随后开始执行,此时a->a1()的指针指向B::a1(),于是乎,我们成功地用父类指针A * a调用了子类B的私有函数。

    

玩坏C++的多态机制