c++中虚多态的实现机制


參考博客:http://blog.csdn.net/neiloid/article/details/6934135

  1. 序言
  2. 证明vptr指针存在
  3. 无继承
  4. 单继承无覆盖
  5. 单继承有覆盖
  6. 多继承有覆盖
  7. 总结

1.序言

这篇博文探讨c++内部多态的实现机制,參考书主要是《深入探索c++对象模型》。因为本人水平有限,望读者指正。

实现多态有3点前提,即继承。虚化,基类指针指向派生类对象。

本质而言,整体来说,当类的内部通过virtual关键字声明虚函数时。编译器生成vptr指针,指向虚函数表(虚表),虚表中依次存放着各虚函数(非虚函数不在这里)。

这样。就能够通过指针来调用这些函数。

PS:执行环境为VC++6.0。因为编译器的差异而产生的不同本文不讨论。


2.证明vptr指针的存在

#include<iostream>
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;}
};
int main (void)
{
Base b;
cout<<"证明vptr的存在"<<endl;
cout<<"sizeof b = " <<sizeof b<<endl;
return 0;
}



ps:linux下 sizeof b = 8


3无继承

#include<iostream>
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;}
}; int main (void)
{
typedef unsigned long P_FUN;//指针类型为无符号长整型
typedef void(*FUN)(void);//声明函数指针 FUN f,g,h;
Base b; f=(FUN)(*(P_FUN*)*(P_FUN*)&b);
g=(FUN)(*((P_FUN*)*(P_FUN*)&b+1));
h=(FUN)(*((P_FUN*)*(P_FUN*)&b+2)); f();
g();
h(); cout<<(FUN)(*((P_FUN*)*(P_FUN*)&b+3))<<endl;
return 0;
}



这样就通过指针的方式调用了续表中的方法函数。

通过现象。能够大概预计出内存的布局。例如以下图。

结束位置以后可能为操作系统内部的仅仅读空间,訪问会发生段错误。

ps:linux下结束位置可能为0。可能为1,有兴趣的读着能够研究一下。


4.单继承无覆盖

#include<iostream>
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 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;} };
int main (void)
{
typedef unsigned long P_FUN;
typedef void(*FUN)(void); FUN f,g,h,f1,g1,h1;
Derive b; f=(FUN)(*(P_FUN*)*(P_FUN*)&b);
g=(FUN)(*((P_FUN*)*(P_FUN*)&b+1));
h=(FUN)(*((P_FUN*)*(P_FUN*)&b+2));
f1=(FUN)(*((P_FUN*)*(P_FUN*)&b+3));
g1=(FUN)(*((P_FUN*)*(P_FUN*)&b+4));
h1=(FUN)(*((P_FUN*)*(P_FUN*)&b+5)); f();
g();
h();
f1();
g1();
h1(); cout<<(FUN)(*((P_FUN*)*(P_FUN*)&b+6))<<endl;
return 0;
}



依据结果能够看出,无覆盖的情况下,派生类的虚函数直接放在第一张虚表后边。


5.单继承有覆盖

#include<iostream>
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 Derive:public Base
{
public:
virtual void f(){cout<<"Derive::f1()"<<endl;}
virtual void g1(){cout<<"Derive::g1()"<<endl;}
virtual void h1(){cout<<"Derive::h1()"<<endl;} };
int main (void)
{
typedef unsigned long P_FUN;
typedef void(*FUN)(void); FUN f,g,h,f1,g1,h1;
Derive b; f1=(FUN)(*(P_FUN*)*(P_FUN*)&b);
g=(FUN)(*((P_FUN*)*(P_FUN*)&b+1));
h=(FUN)(*((P_FUN*)*(P_FUN*)&b+2));
g1=(FUN)(*((P_FUN*)*(P_FUN*)&b+3));
h1=(FUN)(*((P_FUN*)*(P_FUN*)&b+4)); f1();
g();
h();
g1();
h1(); cout<<(FUN)(*((P_FUN*)*(P_FUN*)&b+5))<<endl;
return 0;
}



依据执行结果能够看出,有覆盖的情况下,派生类虚函数覆盖到基类的虚函数,不覆盖的虚函数依次放在第一张表的后面。

6多继承有覆盖

#include<iostream>
using namespace std;
class Base1
{
private:
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 Derive:public Base1,public Base2
{
public:
virtual void f(){cout<<"Derive::f1()"<<endl;}
virtual void g1(){cout<<"Derive::g1()"<<endl;}
virtual void h1(){cout<<"Derive::h1()"<<endl;}
};
int main (void)
{
typedef unsigned long P_FUN;
typedef void(*FUN)(void); FUN B1f,B1g,B1h,B2f,B2g,B2h,Dg,Dh;
Derive b; B1f=(FUN)(*(P_FUN*)*((P_FUN*)&b));
B1g=(FUN)(*((P_FUN*)*((P_FUN*)&b)+1));
B1h=(FUN)(*((P_FUN*)*((P_FUN*)&b)+2)); B2f=(FUN)(*(P_FUN*)*((P_FUN*)&b+1));
B2g=(FUN)(*((P_FUN*)*((P_FUN*)&b+1)+1));
B2h=(FUN)(*((P_FUN*)*((P_FUN*)&b+1)+2)); Dh=(FUN)(*((P_FUN*)*((P_FUN*)&b)+3));
Dg=(FUN)(*((P_FUN*)*((P_FUN*)&b)+4)); B1f();
B1g();
B1h();
cout<<endl; Dh();
Dg();
cout<<endl; B2f();
B2g();
B2h();
cout<<endl; cout<<(FUN)(*((P_FUN*)*((P_FUN*)&b)+5))<<endl;
cout<<(FUN)(*((P_FUN*)*((P_FUN*)&b+1)+3))<<endl; return 0;
}



依据结果可知,多继承下,覆盖的派生类函数会把全部的虚表中相相应的基类虚函数覆盖掉,不覆盖的派生类虚函数仅仅放在第一张虚表的后面。其它虚表后没有。


7.总结

1.当类的内部通过virtual关键字声明虚函数时,编译器生成vptr指针,指向虚函数表(虚表),虚表中依次存放着各虚函数(非虚函数不在当中)。

2.无覆盖的情况下。派生类的虚函数直接放在第一张虚表后边。

3.有覆盖的情况下。派生类虚函数覆盖到基类的虚函数,不覆盖的虚函数依次放在第一张表的后面。

4.多继承下,覆盖的派生类函数会把全部的虚表中相相应的基类虚函数覆盖掉,不覆盖的派生类虚函数仅仅放在第一张虚表的后面,其它虚表后没有。

PS:c++是一门威力十分强大的语言。内部构造复杂,或许有人说这些内容没什么卵用,但本人认为深入理解一下绝不是坏事,本文阅读的难点在于指针的应用,事实上整个c/c++的强大也是得益于指针,希望大家通过本文对c/c++有更深入的理解。

另外说明一下。指针确实是不安全的,比如本文中全部的public关键字删掉之后,通过指针仍然能够调用出类内的私有成员方法。

本人是一个即将大三的本科生,能力有限。博文如有不妥支出望各位读者指正。

c++中虚多态的实现机制的更多相关文章

  1. C++中虚函数功能的实现机制

    要理解C++中虚函数是如何工作的,需要回答四个问题. 1.  什么是虚函数. 虚函数由于必须是在类中声明的函数,因此又称为虚方法.所有以virtual修饰符开始的成员函数都成为虚方法.此时注意是vir ...

  2. 【高级】C++中虚函数机制的实现原理

    多态是C++中的一个重要特性,而虚函数却是实现多态的基石.所谓多态,就是基类的引用或者指针可以根据其实际指向的子类类型而表现出不同的功能.这篇文章讨论这种功能的实现原理,注意这里并不以某个具体的编译器 ...

  3. 第二十二篇:C++中的多态机制

    前言 封装性,继承性,多态性是面向对象语言的三大特性.其中封装,继承好理解,而多态的概念让许多初学者感到困惑.本文将讲述C++中多态的概念以及多态的实现机制. 什么是多态? 多态就是多种形态,就是许多 ...

  4. 简述C++中的多态机制

    前言 封装性,继承性,多态性是面向对象语言的三大特性.其中封装,继承好理解,而多态的概念让许多初学者感到困惑.本文将讲述C++中多态的概念以及多态的实现机制. 什么是多态? 多态就是多种形态,就是许多 ...

  5. 深入Java核心 Java中多态的实现机制(1)

    在疯狂java中,多态是这样解释的: 多态:相同类型的变量,调用同一个方法时,呈现出多中不同的行为特征, 这就是多态. 加上下面的解释:(多态四小类:强制的,重载的,参数的和包含的) 同时, 还用人这 ...

  6. java中实现多态的机制是什么?

    多态性是面向对象程序设计代码重用的一个重要机制,我们曾不只一次的提到Java多态性.在Java运行时多态性:继承和接口的实现一文中,我们曾详细介绍了Java实现运行时多态性的动态方法调度:今天我们再次 ...

  7. C++中的多态及虚函数大总结

    多态是C++中很关键的一部分,在面向对象程序设计中的作用尤为突出,其含义是具有多种形式或形态的情形,简单来说,多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为.即用一个函数名可以调用 ...

  8. c++中的多态机制

    目录 1  背景介绍 2  多态介绍 2-1  什么是多态 2-2  多态的分类 2-3  动态多态成立的条件 2-4  静态联编和动态联编 2-5  动态多态的实现原理    2-6   虚析构函数 ...

  9. 聊聊 C# 中的多态底层 (虚方法调用) 是怎么玩的

    最近在看 C++ 的虚方法调用实现原理,大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针,而 vtable array 中的每一个指针元素指向的就是各自的 ...

随机推荐

  1. 3.Apache ZooKeeper数据模型

    1. ZooKeeper自下向上的服务视图 Apache ZooKeeper是分布式应用程序的协调服务. 它旨在解决分布式应用程序中与组件协调相关的棘手问题. 它通过暴露一个简单而强大的接口来实现这一 ...

  2. ldap数据库--ODSEE--卸载

    针对ldap实例的卸载,即删除,可以通过管理界面进行操作也可以通过命令行进行操作.卸载顺序为ldap实例--agent--ads.这里主要介绍命令操作步骤 1,ldap实例卸载 从ads注销,即不在需 ...

  3. bash, sh, dash 傻傻分不清楚

    原文链接,转载请注明出处: http://www.happycxz.com/m/?p=137 常见shell类型 Bourne shell (sh) UNIX 最初使用,且在每种 UNIX 上都可以使 ...

  4. 用C#实现DES加密解密解决URL参数明文的问题

    啥也不说,直接上代码. 加密解码,封装到一个类,key可以自己修改. using System; using System.Security.Cryptography; using System.Te ...

  5. 8. 理解ZooKeeper的内部工作原理

    到目前为止,我们已经讨论了ZooKeeper服务的基础知识,并详细了解了数据模型及其属性. 我们也熟悉了ZooKeeper 监视(watch)的概念,监视就是在ZooKeeper命名空间中的znode ...

  6. pandas基本介绍-【老鱼学pandas】

    前面我们学习了numpy,现在我们来学习一下pandas. Python Data Analysis Library 或 pandas 主要用于处理类似excel一样的数据格式,其中有表头.数据序列号 ...

  7. cgg之字面值

    字面值时源代码中用来描述固定值的记号,可能是整数.浮点数.字符或者字符串 2.1 整数常量 除了常见的十进制数外,还有八进制(以数字0开头)或者十六进制(0x/0X)表示法. #include < ...

  8. Java 中实现方法重试的一种机制

    有这样一个需求,当调用某个方法抛出异常,比如通过 HttpClient 调用远程接口时由于网络原因报 TimeOut 异常:或者所请求的接口返回类似于"处理中"这样的信息,需要重复 ...

  9. python打包exe pyinstaller 简单使用

    源由 最近公司让做了一个小工具,使用python写的,写完之后要求能放在其它电脑上运行,于是就开始寻找方案; 按网上的说法 py2exe已经很久没更新了,资料也不多: 于是就采用pyinstaller ...

  10. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...