C++ 由虚基类 虚继承 虚函数 到 虚函数表

 

 


//虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。 class Base1{ public: Base1(){cout<<"Construct Base1!"<<endl;}; void foo();//普通函数 virtual void foo1(){cout<<"foo1 in Base1"<<endl;};//虚函数:可以在基类中实现(+{})或者直接定义成虚基类,\ 会出现错误:undefined reference to vtable for lass virtual void foo2() = 0;//纯虚函数:必须要在继承类中实现,类似java中的接口好处是 可以在不清楚具体构建的情况下,给派生类规定好规范 }; class Base2{ public: void foo();//普通函数 virtual void foo1();//虚函数 //virtual void foo2() = 0;//纯虚函数 }; //虚继承:继承类继承了基类多次,从而产生了多个拷贝,虚基类的基本原则是在内存中只有基类成员的一份拷贝。\
//这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。 class Derived1: public virtual Base1{ public: Derived1(){cout<<"Construct Derived1~ !"<<endl;}; void foo2(){ cout<<"foo2 in Derive1"<<endl; } }; //多继承 class Derived2: public virtual Base1, public virtual Base2{ void foo2(){ cout<<"foo2 in Derive1"<<endl; } };
错误解决:

在使用虚函数的程序中,编译时会出现

      undefined reference to `vtable for Class 

或    undefined reference to typeinfo for Class   的情况

其解决方案就是将类似于

virtual void foo();
Should be defined (inline or in a linked source file): virtual void foo() {}
Or declared pure virtual: virtual void foo() = 0;
 

基类指针指向派生类  

基类指针可以指向派生类,但是无法使用不存在于基类只存在于派生类的元素。(所以我们需要虚函数和纯虚函数)

  在内存中,一个基类类型的指针是覆盖N个单位长度的内存空间。
  当其指向派生类的时候,由于派生类元素在内存中堆放是:前N个是基类的元素,N之后的是派生类的元素。
  于是基类的指针就可以访问到基类也有的元素了,但是此时无法访问到派生类(就是N之后)的元素。

派生类指针不能指向基类

虚函数表

  本文部分内容参考了 陈皓专栏的内容.

  对c++了解的人都应该知道虚函数是通过一张虚函数表实现的。简称V-Table。 在这个表中,主要是一个类的虚函数的地址表,这张表解决了 继承、覆盖的问题,保证内容真实反映实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子 类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。C++的编译器保证虚函数表的指针存在与对象实例中的最前面 的位置(这是为了保证取道虚表函数的高性能)。 

  在每一个包含虚函数的类中,都存在一张虚函数表,用来记录虚函数真正的地址。当派生类继承于基类时,将基类的虚函数表也一同继承,然后根据自己是否覆盖基类的函数,来修改虚函数表。具体看这个:讲的很详细。实际上是每一个类都会创建一张虚表,每一个类的实例也就从类中得到了一张虚表.

  

  一般: 无覆盖

http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable2.JPG

  一般继承: 虚函数覆盖

 

  多重继承:无覆盖

  

  可以看到子类函数的虚表就接在第一个基类虚表的后面

 

  多重继承: 有虚函数覆盖

需要注意的问题:

  不能通过父类型的指针访问子类自己的函数。即使这种目的可以使用指针的方式访问来达到

  包含任一纯虚函数的类 为 抽象类。抽象类是不能够实例化的。

  

#include <iostream>
#include <cstdio>
using namespace std;

class Base{
public:
     virtual void f(){ cout<<" Base::f "<<endl; }
     virtual void g(){ cout<<" Base::g "<<endl; }
     virtual void h(){ cout<<" Base::h "<<endl; }
};

class Base1{
public:
     virtual void f(){ cout<<" Base1::f "<<endl; }
     virtual void g(){ cout<<" Base1::g "<<endl; }
     virtual void h(){ cout<<" Base1::h "<<endl; }
};

class Base2{
public:
     virtual void f(){ cout<<" Base2::f "<<endl; }
     virtual void g(){ cout<<" Base2::g "<<endl; }
     virtual void h(){ cout<<" Base2::h "<<endl; }
};

class Base3{
public:
     virtual void f(){ cout<<" Base3::f "<<endl; }
     virtual void g(){ cout<<" Base3::g "<<endl; }
     virtual void h(){ cout<<" Base3::h "<<endl; }
};

class Derive : public Base{
public:
     virtual void f1(){ cout<<" Derive::f1 "<<endl; }
     virtual void g1(){ cout<<" Derive::g1 "<<endl; }
     virtual void h1(){ cout<<" Derive::h1"<<endl; }
};

class Derive1 : public Base{
public:
     virtual void f(){ cout<<" Derive1::f"<<endl; }
     virtual void g1(){ cout<<" Derive1::g1 "<<endl; }
     virtual void h1(){ cout<<" Derive1::h1"<<endl; }
};

class Derive2 : public Base1, public Base2, public Base3{
public:
     virtual void f1(){ cout<<" Derive2::f1"<<endl; }
     virtual void g1(){ cout<<" Derive2::g1 "<<endl; }
};

class Derive3: public Base1, public Base2, public Base3{
public:
     virtual void f(){ cout<<" Derive3::f"<<endl; }
     virtual void g1(){ cout<<" Derive3::g1 "<<endl; }
};

typedef void(*Fun)(void);
//作为函数指针 你得规定好指向的函数的参数类型,返回值类型.

int main(void)
{
     Base b;
     Fun pFun = NULL;
     cout<<"虚函数表地址: "<<&b<<endl;
     cout<<"虚函数表地址: "<<(void *)&b<<endl;
     cout<<"虚函数表地址: "<<(int*)&b<<endl;
     cout<<"虚函数表地址: "<<(long*)&b<<endl;
     /*上面四种输出结果一致 , b的地址转换成何种类型的指针看似是无所谓*/
     cout<<"虚函数表---第一个函数地址: "<<(int *)*(int *)(&b)<<endl;
     cout<<"虚函数表---第一个函数地址: "<<(void *)*(int*)(&b)<<endl;
     /*
       * long:在32位系统是32位整型,取值范围为-2^31 ~ (2^31 - 1);在64位系统是64位整型,取值范围为-2^63 ~ (2^63 - 1)
       * 为了能在64位机器上运行,这里要用 long 而不是int
       */
     pFun = (Fun)*((long *)*(long*)(&b)); //Base::f
     pFun = (Fun)*((long *)*(long*)(&b) + 1); //Base::g
     pFun = (Fun)*((long *)*(long*)(&b) + 2); //Base::h
     /* 我们就可以通过上面的方法调用函数 */


     /*
       *虚函数表明明显是在子类继承父类的时候能够发挥作用, 这里继承可以分为两类:
       * 一般继承(无虚函数覆盖 & 有虚函数覆盖) 和 多重继承(有 & 无 虚函数覆盖)
       */


     /* 一般: 无覆盖*/
     Derive d;
     pFun = (Fun)*((long *)*(long*)(&d) + 3); //Derive::f1

     /*
     *    1)虚函数按照其声明顺序放于表中。
     *    2)父类的虚函数在子类的虚函数前面。
     */


     /* 一般继承: 有虚函数覆盖 */
     Derive1 d1;
     pFun = (Fun)*((long *)*(long*)(&d1)); //Derive::1
     pFun = (Fun)*((long *)*(long*)(&d1) + 1); //Base::g
     /*
     *    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
     *    2)没有被覆盖的函数依旧。
     */


     /*   多重继承: 无覆盖*/
     Derive2 d2;
     pFun = (Fun)*((long *)*(long *)(&d2)); //Base::f
     pFun = (Fun)*((long *)*(long *)(&d2) + 3); //Derive2::f
     pFun = (Fun)*((long *)*(  (long *)(&d2)  + 1)); // Base2: f  这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
     /*
     *1)  每个父类都有自己的虚表。
     *2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
     */


     /* 多重继承: 有虚函数覆盖*/
     Derive3 d3;
     pFun = (Fun)*((long *)*(  (long *)(&d3) ) ); //Derive3: f
     pFun = (Fun)*((long *)*(  (long *)(&d3)  + 1)); // Derive3: f
     pFun = (Fun)*((long *)*(  (long *)(&d3)  + 2)); // Derive3: f  这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
     /*这里设计到了虚表的概念,也就是每一个基类在子类的虚表中都占有一席 , 我们当然可以通过上面这行code来访问其他虚表,
       *但是还可以用二维数组来访问,更加便捷
       */
     long **pVtab = (long **)&d3;
     //Base1 's vtable
     pFun = (Fun)pVtab[0][0];//Derive3::f
     pFun = (Fun)pVtab[0][1];//Base::g
     //Derive's vtable
      pFun = (Fun)pVtab[0][3]; //Derive3::g1
      //The tail of the vtable
     pFun = (Fun)pVtab[0][4];

     /*
     *三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
     *我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
     *这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
     */
     Base1 *b1 = &d3;
     Base2 *b2 = &d3;
     Base3 *b3 = &d3;
     b1->f(); //Derive3::f()
     b2->f(); //Derive3::f()
     b3->f(); //Derive3::f()
     b1->g(); //Base1::g()
     b2->g(); //Base2::g()
     b3->g(); //Base3::g()
     /* 当然,父类指针只能指向子类中覆盖的父类中的虚函数, 如下,尝试访问子类中自有的函数 会报错*/
     Base1 *b4 = new Derive3();
     //b4 -> g1();///error: ‘class Base1’ has no member named ‘g1’|

     /* 但是并不安全:
     * 1) 通过父类型的指针访问子类自己的虚函数: 在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
     * 2) 访问non-public的虚函数:
     */

     return 0;
}

#########################################################################################################

#再说C++多态 #

#########################################################################################################

.  “C++的多态性用一句话概括就是:在基类的函数前+vritual关键字,在派生类中重写该函数,运行时会根据对象的实际类型来调用相应的函数(晚绑定或者叫动态绑定)。如果对象类型是基类,那么调用基类的函数。如果是派生类,那么调用派生类的函数。”

    1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。  

    2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。  

    3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。  

    4:多态用虚函数来实现,结合动态绑定.  

    5:纯虚函数是虚函数再加上 = 0;  

    6:抽象类是指包括至少一个纯虚函数的类。

    纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

   编译器在编译的时候,发现Base类中有虚函数,这时候编译器会为每个包含虚函数的类创建一个虚表(vtable),该表是一个一维数组,在这个数组中存放每一个函数的地址。

  如何定位虚表?编译器提供另外一个结构虚指针(vptr)。虚指针指向虚表,虚表中存放的是函数的地址。在程序运行时,根据对象的类型去初始化vptr,从而让vptr指向正确的所属类的虚表。由于pFather实际指向的对象类型是Son,所以vptr指向Son的vtable。

posted on 2016-09-17 16:05  暴力的轮胎  阅读(1475)  评论(0编辑  收藏  举报

导航