精华内容
下载资源
问答
  • 虚函数实现原理

    2017-05-10 10:31:39
    虚函数实现原理

    虚函数实现原理


    http://blog.csdn.net/wanghaobo920/article/details/7674631



    C/C++杂记:虚函数的实现的基本原理

    http://www.cnblogs.com/malecrab/p/5572730.html


    C/C++杂记:深入虚表结构


    http://www.cnblogs.com/malecrab/p/5573368.html


    20170327_请说出虚函数的工作原理

    http://blog.csdn.net/cmm0401/article/details/66971032


    当前标签: C/C++:

    http://www.cnblogs.com/malecrab/tag/C%2FC%2B%2B/

    C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
    C/C++杂记:深入虚表结构
    C/C++杂记:虚函数的实现的基本原理
    C/C++杂记:深入理解数据成员指针、函数成员指针
    C/C++杂记:NULL与0的区别、nullptr的来历



    展开全文
  • C++虚函数实现原理

    2020-04-26 23:12:15
    C++虚函数实现原理前言实现机制实例分析测试代码及输入结果结果分析 前言 C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数。 实现机制 每个类对象添加一个成员,该成员中保存了一个...

    前言

    C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数。

    实现机制

    每个类对象添加一个成员,该成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),该数组称为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。

    实例分析

    测试代码及输入结果

    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
    	virtual void a() { cout << "Base fun a" << endl; }
    	virtual void b() { cout << "Base fun b" << endl; }
    	virtual void c() { cout << "Base fun c" << endl; }
    private:
    	int x;
    };
    
    class Child : public Base
    {
    	virtual void b() { cout << "Child fun b" << endl; }
    };
    
    int main()
    {
    	typedef void(*Fun)(void);
    
    	Child c;
    	int x = sizeof(c);/* x=8 (x86) */
    	int* pBase = (int*)(&c);
    	cout << "对象地址:" << pBase << endl;
    	int* pFun = (int*)(*pBase);
    	cout << "虚函数表地址:" << pFun << endl;
    
    	Fun pFun1 = (Fun)*(pFun + 0);
    	pFun1();
    	Fun pFun2 = (Fun)*(pFun + 1);
    	pFun2();
    	Fun pFun3 = (Fun)*(pFun + 2);
    	pFun3();
    
        return 0;
    }
    

    说明:定义了基类Base,包含三个虚函数a、b、c和一个int成员变量x,子类Child重新实现了函数b。
    在vs2015 x86下编译运行输出如下:
    对象地址:0036F7BC
    虚函数表地址:011A9C80
    Base fun a
    Child fun b
    Base fun c

    结果分析

    1. sizeof(C)结果为8,是因为在32位处理器上,虚标指针占用4字节,成员变量x占用4字节。
    2. 查看对象地址0x0036F7BC,其内存前四个字节正好是虚函数表的地址0x011A9C80(由于inter处理器是小端模式,所以字节序看起来刚好相反)。
      对象内存分布
    3. 查看虚函数表地址处的内存0x011A9C80,可以看到对象c包含的虚函数的具体地址,其中第一个和第三个为基类Base的a、c函数地址,第二个为子类Child的b函数地址。
      虚函数表内存分布
    4. 对象c在内存中函数地址分布示意图如下图所示:

    虚函数表内存分布示意图
    虚函数表指针保存在对象起始地址的前4字节(x64为8字节),其后紧跟的为成员变量。注意,前4字节是只存储了对象的虚函数表的地址,虚函数表存储在该地址指向的地址区。

    展开全文
  • 下面小编就为大家带来一篇浅谈C++中虚函数实现原理揭秘。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • C++虚函数实现原理探究概念代码示例分析 概念 在面向对象编程中,多态指的是一个接口有不同的实现方式。在C++中,多态是重要的概念之一。其中虚函数表的主要作用就是为了实现多态的机制。 C++支持两种多态性:编译时...

    C++虚函数实现原理探究

    概念

    在面向对象编程中,多态指的是一个接口有不同的实现方式。在C++中,多态是重要的概念之一。其中虚函数表的主要作用就是为了实现多态的机制。

    C++支持两种多态性:编译时多态性,运行时多态性。
    a.编译时多态性:通过重载函数实现
    b 运行时多态性:通过虚函数实现。

    首先我们讨论下为什么需要多态概念的引入:我们设置很多小动物,猪,牛,羊等等,每个动物都有自己独特的叫声,但每个动物又都有共同的属性,比如睡觉,吃饭等等。于是我们要为每一个动物创建一个call()方法,表示不同的动物在叫,又必须为每一个动物创建eat,sleep方法,然后因为eat,sleep是属于所有动物的共同的属性,写很多遍eat,sleep内容一样的方法是无法忍受的,因此我们引入了抽象的概念,可以把各类动物都抽象为动物,每个动物又可以实现自己特有的属性call,共有属性eat,sleep。这个在C++中是怎么实现的呢?答案就在今天我们要探讨的虚函数中。我们可以创建猪,牛,羊的方法,通过动物*指向不同的实例,这样,在调用牛->吃的时候,就是调用动物这个类中的实现,而调用牛->叫的时候,我们可以直接调用牛这个类的实现。但具体我们应该怎么实现呢?这里,我们引入virtual关键词,即通过虚函数表实现。
    下面我们引入一段代码:

    代码示例

    #include <iostream>
    using namespace std;
    class animal
    {
    public:
    	virtual void say()
    	{
    		cout << "i am animal" << endl;
    	}
    	virtual void eat()
    	{
    		cout << "i am animal, i am eat" << endl;
    	}
    	virtual void sleep()
    	{
    		cout << "i am animal, i am sleep" << endl;
    	}
    };
    class cattle : public animal
    {
    public:
    	void say()
    	{
    		cout << "i am cattle" << endl;
    	}
    };
    class pig : public animal
    {
    public:
    	void say()
    	{
    		cout << "i am pig" << endl;
    	}
    };
    class sheep : public animal
    {
    public:
    	void say()
    	{
    		cout << "i am sheep" << endl;
    	}
    };
    int main()
    {
    	cout << "virtual test" << endl;
    	animal* c = new cattle;
    	c->say();
    	c->sleep();
    	c->eat();
    	animal* s = new sheep;
    	s->say();
    	animal* p = new pig;
    	p->say();
    
    	getchar();
    	return 0;
    }
    

    输出结果:
    这里,所有的指针类型都是animal类型,指向了不同的实例,所以对应的调用结果也不一样的
    根据上述结果,我们分析一下,所有的指针类型都是animal类型,指向了不同的实例,所以对应的调用结果也不一样的。

    分析

    那具体的虚函数表在系统中是如何实现的呢?
    在这里插入图片描述

    在animal的实例中,保存了一个_vfptr的虚函数表,因此可以根据表中存储的函数指针,定位具体需要调用的函数,如果实例指向cattle,则animal::say()对应的单元格存储的就是cattle::say()的函数指针。
    在这里插入图片描述
    至此,我们明白了多态是通过虚函数表的方式实现的,并且通过上述截图,发现需要中的cattle::say(),animal::eat(),animal::sleep()均是void类型,因此,这里是通过虚函数表保存函数指针的方式实现的。因此,上述调用中,c->say();实际调用的是c->(_vfptr[0])。

    对于上述例子,animal类中也有say方法的实现,但是在子类中,被覆盖了,因此在虚表中也被其覆盖了,如果子类中不对say进行实现,则需要中会指向animal::say()。我们也可以将animal的say方法作为纯虚函数进行设定,即

    virtual void say() = 0;
    

    这样,每一个子类在继承了父类animal后,都必须有自己的特化实现。

    展开全文
  • c++虚函数实现原理

    2016-01-12 18:07:19
    c++虚函数实现原理 - [c++] 2011-09-15 版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明 http://www.blogbus.com/wanderer-zjhit-logs/161830653.html FROM: ...

    • c++虚函数实现原理 - [c++]

      2011-09-15

      版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
      http://www.blogbus.com/wanderer-zjhit-logs/161830653.html

      FROM: http://pcedu.pconline.com.cn/empolder/gj/c/0503/574706_3.html
      类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visual BASIC 6.0 是典型的非面向对象的开发语言,但是它的确是支持类,支持类并不能说明就是支持面向对象,能够解决多态问题的语言,才是真正支持面向对象的开发的语言,所以务必提醒有过其它非面向对象语言基础的读者注意!


      多态的这个概念稍微有点模糊,如果想在一开始就想用清晰用语言描述它,让读者能够明白,似乎不太现实,所以我们先看如下代码:

      //例程1
      #include <iostream>     
      using namespace std;   
         
      class Vehicle
      {   
      public:   
           Vehicle(float speed,int total)
           {
               Vehicle::speed=speed;
               Vehicle::total=total;
           }
          void ShowMember()
           {
              cout<     }
      protected:   
          float speed;
          int total;
      };   
      class Car:public Vehicle   
      {   
      public:   
           Car(int aird,float speed,int total):Vehicle(speed,total)   
           {   
               Car::aird=aird;   
           }
          void ShowMember() 但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:

      //例程2
      #include <iostream>     
      using namespace std;   
         
      class Vehicle
      {   
      public:   
           Vehicle(float speed,int total)
           {
               Vehicle::speed=speed;
               Vehicle::total=total;
           }
          void ShowMember()
           {
              cout<     }
      protected:   
          float speed;
          int total;
      };   
      class Car:public Vehicle   
      {   
      public:   
           Car(int aird,float speed,int total):Vehicle(speed,total)   
           {   
               Car::aird=aird;   
           }
          void ShowMember()
           {
              cout<     }
      protected:   
          int aird;
      };   

      void test(Vehicle &temp)
      {
           temp.ShowMember();
      }

      void main()   
      {
           Vehicle a(120,4);
           Car b(180,110,4);
           test(a);
           test(b);
          cin.get();
      }

         例子中,对象a与b分辨是基类和派生类的对象,而函数test的形参却只是Vehicle类的引用,按照类继承的特点,系统把Car类对象看做是一个 Vehicle类对象,因为Car类的覆盖范围包含Vehicle类,所以test函数的定义并没有错误,我们想利用test函数达到的目的是,传递不同 类对象的引用,分别调用不同类的,重载了的,ShowMember成员函数,但是程序的运行结果却出乎人们的意料,系统分不清楚传递过来的基类对象还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。


           {
              cout<     }
      protected:   
          int aird;
      };   

      void main()   
      {   
           Vehicle a(120,4);
           a.ShowMember();
           Car b(180,110,4);
           b.ShowMember();
          cin.get();
      }

         在c++中是允许派生类重载基类成员函数的,对于类的重载来说,明确的,不同类的对象,调用其类的成员函数的时候,系统是知道如何找到其类的同名成员, 上面代码中的a.ShowMember();,即调用的是Vehicle::ShowMember(),b.ShowMember();,即调用的是 Car::ShowMemeber();。

      但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:

      //例程2
      #include <iostream>     
      using namespace std;   
         
      class Vehicle
      {   
      public:   
           Vehicle(float speed,int total)
           {
               Vehicle::speed=speed;
               Vehicle::total=total;
           }
          void ShowMember()
           {
              cout<     }
      protected:   
          float speed;
          int total;
      };   
      class Car:public Vehicle   
      {   
      public:   
           Car(int aird,float speed,int total):Vehicle(speed,total)   
           {   
               Car::aird=aird;   
           }
          void ShowMember()
           {
              cout<     }
      protected:   
          int aird;
      };   

      void test(Vehicle &temp)
      {
           temp.ShowMember();
      }

      void main()   
      {
           Vehicle a(120,4);
           Car b(180,110,4);
           test(a);
           test(b);
          cin.get();
      }

         例子中,对象a与b分辨是基类和派生类的对象,而函数test的形参却只是Vehicle类的引用,按照类继承的特点,系统把Car类对象看做是一个 Vehicle类对象,因为Car类的覆盖范围包含Vehicle类,所以test函数的定义并没有错误,我们想利用test函数达到的目的是,传递不同 类对象的引用,分别调用不同类的,重载了的,ShowMember成员函数,但是程序的运行结果却出乎人们的意料,系统分不清楚传递过来的基类对象还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。

      为了要解决上述不能正确分辨对象类型的问题,c++提供了一种叫做多态性(polymorphism)的技术来解决问题,对于例程序1,这种能够在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(early binding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding),下面我们要看的例程3,就是滞后联编,滞后联编正是解决多态问题的方法。

      代码如下:

      //例程3
      #include <iostream>     
      using namespace std;   
         
      class Vehicle
      {   
      public:   
           Vehicle(float speed,int total)
           {
               Vehicle::speed = speed;
               Vehicle::total = total;
           }
          virtual void ShowMember()//虚函数
           {
              cout<     }
      protected:   
          float speed;
          int total;
      };   
      class Car:public Vehicle   
      {   
      public:   
           Car(int aird,float speed,int total):Vehicle(speed,total)   
           {   
               Car::aird = aird;   
           }
          virtual void ShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加
           {
              cout<     }
      public:   
          int aird;
      };

      void test(Vehicle &temp)
      {
           temp.ShowMember();
      }

      int main()   
      {   
           Vehicle a(120,4);
           Car b(180,110,4);
           test(a);
           test(b);
          cin.get();
      }

        多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上virtual关键字,那么该成员函数就变成了虚函数,从上例代码运行的结果看,系统成功的分辨出了对象的真实类型,成功的调用了各自的重载成员函数。

        多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。

       

      虚函数的定义要遵循以下重要规则:

        1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

      2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

      3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

      4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义定义,但是在编译的时候系统仍然将它看做是非内联的。

      5.构造函数不能是虚函数,因为构造的时候,对象还是一片位定型的空间,只有构造完成后,对象才是具体类的实例。

      6.析构函数可以是虚函数,而且通常声名为虚函数。


      说明一下,虽然我们说使用虚函数会降低效率,但是在处理器速度越来越快的今天,将一个类中的所有成员函数都定义成为virtual总是有好处的,它除了会增加一些额外的开销是没有其它坏处的,对于保证类的封装特性是有好处的。

        对于上面虚函数使用的重要规则6,我们有必要用实例说明一下,为什么具备多态特性的类的析构函数,有必要声明为virtual

      代码如下:

      #include <iostream>     
      using namespace std;   
         
      class Vehicle
      {   
      public:  
           Vehicle(float speed,int total)
           {
               Vehicle::speed=speed;
               Vehicle::total=total;
           }
          virtual void ShowMember()
           {
              cout<     }
          virtual ~Vehicle()
           {
              cout<<"载入Vehicle基类析构函数"<        cin.get();
           }
      protected:   
          float speed;
          int total;
      };   
      class Car:public Vehicle   
      {   
      public:   
           Car(int aird,float speed,int total):Vehicle(speed,total)   
           {   
               Car::aird=aird;   
           }
          virtual void ShowMember()
           {
              cout<     }
          virtual ~Car()
           {
              cout<<"载入Car派生类析构函数"<        cin.get();
           }
      protected:   
          int aird;
      };   

      void test(Vehicle &temp)
      {
           temp.ShowMember();
      }
      void DelPN(Vehicle *temp)
      {
          delete temp;
      }
      void main()
      {   
           Car *a=new Car(100,1,1);
           a->ShowMember();
           DelPN(a);
          cin.get();
      }

         从上例代码的运行结果来看,当调用DelPN(a);后,在析构的时候,系统成功的确定了先调用Car类的析构函数,而如果将析构函数的virtual 修饰去掉,再观察结果,会发现析构的时候,始终只调用了基类的析构函数,由此我们发现,多态的特性的virtual修饰,不单单对基类和派生类的普通成员 函数有必要,而且对于基类和派生类的析构函数同样重要。

       

       

      详解虚表

      C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员 函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技 术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

      关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

      当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

      言归正传,让我们一起进入虚函数的世界。

      虚函数表

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

      这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是 为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

      听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

      假设我们有这样的一个类:

      class Base {

      public:

      virtual void f() { cout << "Base::f" << endl; }

      virtual void g() { cout << "Base::g" << endl; }

      virtual void h() { cout << "Base::h" << endl; }

      };

      按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

      typedef void(*Fun)(void);

      Base b;

      Fun pFun = NULL;

      cout << "虚函数表地址:" << (int*)(&b) << endl;

      cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

      // Invoke the first virtual function

      pFun = (Fun)*((int*)*(int*)(&b));

      pFun();

      实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

      虚函数表地址:0012FED4

      虚函数表 — 第一个函数地址:0044F148

      Base::f

      通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

      (Fun)*((int*)*(int*)(&b)+0); // Base::f()

      (Fun)*((int*)*(int*)(&b)+1); // Base::g()

      (Fun)*((int*)*(int*)(&b)+2); // Base::h()

      这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

       




      注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这 个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

      下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

      一般继承(无虚函数覆盖)

      下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

       




      请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

      对于实例:Derive d; 的虚函数表如下:

       

       

       

      我们可以看到下面几点:

      1)虚函数按照其声明顺序放于表中。

      2)父类的虚函数在子类的虚函数前面。

      我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

      一般继承(有虚函数覆盖)

      覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

       

       

       

       

      为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

       


       


      我们从表中可以看到下面几点,

      1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

      2)没有被覆盖的函数依旧。

      这样,我们就可以看到对于下面这样的程序,

      Base *b = new Derive();

      b->f();

      由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

      多重继承(无虚函数覆盖)

      下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

       

       

       

       

       

      对于子类实例中的虚函数表,是下面这个样子:

       


       

      我们可以看到:

      1) 每个父类都有自己的虚表。

      2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

      这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

      多重继承(有虚函数覆盖)

      下面我们再来看看,如果发生虚函数覆盖的情况。

      下图中,我们在子类中覆盖了父类的f()函数。

       

       

      下面是对于子类实例中的虚函数表的图:

       

       

       

      我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

      Derive d;

      Base1 *b1 = &d;

      Base2 *b2 = &d;

      Base3 *b3 = &d;

      b1->f(); //Derive::f()

      b2->f(); //Derive::f()

      b3->f(); //Derive::f()

      b1->g(); //Base1::g()

      b2->g(); //Base2::g()

      b3->g(); //Base3::g()

      安全性

      每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

      一、通过父类型的指针访问子类自己的虚函数

      我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

      Base1 *b1 = new Derive();

      b1->f1(); //编译出错

      任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

      二、访问non-public的虚函数

      另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

      如:

      class Base {

      private:

      virtual void f() { cout << "Base::f" << endl; }

      };

      class Derive : public Base{

      };

      typedef void(*Fun)(void);

      void main() {

      Derive d;

      Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

      pFun();

      }

      结束语

      C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。


    展开全文
  • 【转】c++虚函数实现原理 原文链接:https://blog.csdn.net/neiloid/article/details/6934135 C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后...
  • C++中虚函数实现原理揭秘 编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟。 编译器对每个包含虚函数的类创建一个表(称为V TA B L E)。在V TA B L E中,编译器放置特定类的虚函数地址...
  • 从Arm汇编看Android C++虚函数实现原理 Posted on 2016-06-21 | In Android 前言 C++通过虚函数来实现多态,从而在运行时动态决定要调用的函数。那么虚函数的调用过程具体是怎样的呢?本文将基于Arm...
  • 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。例: 其中: B的虚函数表中存放着B::foo和B::bar两个函数指针。 ...
  • 虚函数实现原理分析

    2017-10-16 11:26:58
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,349
精华内容 539
关键字:

虚函数实现原理