虚函数详解第一篇:对象内存模型浅析

C++中的虚函数的内部实现机制到底是怎样的呢?
    鉴于涉及到的内容有点多,我将分三篇文章来介绍。
    第一篇:对象内存模型浅析,这里我将对对象的内存模型进行简单的实验和总结。
    第二篇:继承对象的构造和析构浅析,这里我将对存在继承关系的对象的构造和析构进行简单的实验和总结。
    第三篇:虚函数的内部机制浅析,这里我将对虚函数内部的实现机制进行实验总结。
    我使用的编译器是VS2008,有不足或者不准确的地方,欢迎大家拍砖(我个人非常迫切的希望得到大家的指正),我会及时修正相关内容。
 
    开始正题:对象内存模型浅析:
    
  1. #include <tchar.h>
  2. #include <iostream>
  3. using namespace std;
  4. #pragma pack (1)
  5. class Person
  6. {
  7. private:
  8. int m_nAge;
  9. };
  10. class Man : public Person
  11. {
  12. private:
  13. double m_dHeight;
  14. };
  15. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
  16. {
  17. Person Jack;
  18. Man Mike;
  19. cout << sizeof(Jack) << endl;
  20. cout << sizeof(Mike) << endl;
  21. return 1;
  22. }
    首先解释一下#pragma pack(1)这条语句的作用,它要求编译器将字节对齐的最小单位设定为1个字节。
    关于字节对齐,简单的解释就是,假定一个32位的CPU,读取一个存储在内存中的int型的变量,如果该int变量存放在内存中的首地址是偶地址,那么CPU一个周期就能读出这32bit的数据,如果该int变量存放在内存中的首地址是奇地址,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。所以,如果我们将字节对齐设置为4个字节,那么理论上,CPU执行我们代码的速度要比将字节对齐设置为1个字节的速度要快。
    所以,如果我们将字节对齐设置为8个字节,那么
    int nNum1;
    double dNum2;
将会占用16个字节的大小,而如果我们将字节对齐设置为1个字节,那么它将会占用12个字节的大小,上述代码将字节对齐设置为1个字节,是为了防止字节对齐干扰了我们对于对象内存模型的实验。
    回到主题,上述代码的执行结果如下:
    4
    12
    我们看到,Person类对象占用了4个字节大小的内存空间,Man类对象占用了12个字节的大小的内存空间,所以,Man类中实际上有两个成员变量,int m_nAge和double m_dHeight,所以可以得出一个结论:派生类对象中同时包含基类的成员变量。
    那么它们在内存中的位置是怎样的呢?
  1. #include <tchar.h>
  2. #include <iostream>
  3. using namespace std;
  4. #pragma pack (1)
  5. class Person
  6. {
  7. private:
  8. int m_nAge;
  9. };
  10. class Man : public Person
  11. {
  12. private:
  13. double m_dHeight;
  14. };
  15. class Woman : public Person
  16. {
  17. private:
  18. double m_dWigth;
  19. };
  20. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
  21. {
  22. Person Jack;
  23. Man Mike;
  24. Woman Susan;
  25. cout << &Jack << endl;
  26. cout << &Mike << endl;
  27. cout << &Susan << endl;
  28. return 1;
  29. }
上述代码输出了Person类对象和Man类对象的地址,执行结果如下:
0012FF60
0012FF4C
0012FF38
我们知道,0012FF60和0012FF4C之间有14个字节的内存空间,0012FF38和0012FF4C之间有14个字节的内存空间,我们将Man类对象分成Person基类部分(int m_nAge)和Man派生类部分(double m_dHeight),将Woman类对象分成Person基类部分(int m_nAge)和Woman派生类部分(double m_dWeight)。那么,Man类和Woman类的对象内存模型如下:
 
 
所以,
  • Person Jack;
  • Man Mike;
  • Woman Susan;
这三行代码实际上产生了3个Person基类部分、一个Man派生类部分和一个Woman派生类部分,而非像代码中写的表意那样,有一个Person基类部分,一个Man派生类部分和一个Woman派生类部分。
类的继承和派生只是简化方便我们程序员编写代码,并不会简化派生类对象占用的内存大小。
 
C++中的虚函数的内部实现机制到底是怎样的呢?
    鉴于涉及到的内容有点多,我将分三篇文章来介绍。
    第一篇:对象内存模型浅析,这里我将对对象的内存模型进行简单的实验和总结。
    第二篇:继承对象的构造和析构浅析,这里我将对存在继承关系的对象的构造和析构进行简单的实验和总结。
    第三篇:虚函数的内部机制浅析,这里我将对虚函数内部的实现机制进行实验总结。
    我使用的编译器是VS2008,有不足或者不准确的地方,欢迎大家拍砖(我个人非常迫切的希望得到大家的指正),我会及时修正相关内容。
 

开始正题:继承对象的构造和析构浅析:

    在虚函数详解第一篇中,我简单的介绍了C++对象内存模型。我们了解到派生类对象是由基类部分和派生部分构成的,那么该派生类对象是如何被构造和析构的呢?
    
  1. #include <tchar.h>
  2. #include <iostream>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. Person()
  8. {
  9. cout << _T("基类的构造函数被调用") << endl;
  10. }
  11. ~Person()
  12. {
  13. cout << _T("基类的析构函数被调用") << endl;
  14. }
  15. };
  16. class Man : public Person
  17. {
  18. public:
  19. Man()
  20. {
  21. cout << _T("派生类的构造函数被调用") << endl;
  22. }
  23. ~Man()
  24. {
  25. cout << _T("派生类的析构函数被调用") << endl;
  26. }
  27. };
  28. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
  29. {
  30. Man Mike;
  31. return 1;
  32. }
上述代码的执行结果如下:
我们可以看到:构造一个派生类对象的时候,先调用基类的构造函数,再调用派生类的构造函数,析构一个派生类对象的时候,先调用派生类的析构函数,再调用基类的析构函数。
 
    上述内容讲述的是普通派生类的构造和析构过程,对于具有虚函数的派生类的构造和析构过程是怎样的呢?
    
  1. #include <tchar.h>
  2. #include <iostream>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. Person()
  8. {
  9. cout << _T("基类的构造函数被调用") << endl;
  10. }
  11. virtual void Height()
  12. {
  13. cout << _T("人类具有身高属性") << endl;
  14. }
  15. virtual ~Person()
  16. {
  17. cout << _T("基类的析构函数被调用") << endl;
  18. }
  19. };
  20. class Man : public Person
  21. {
  22. public:
  23. Man()
  24. {
  25. cout << _T("派生类的构造函数被调用") << endl;
  26. }
  27. virtual void Height()
  28. {
  29. cout << _T("男人具有身高属性") << endl;
  30. }
  31. virtual ~Man()
  32. {
  33. cout << _T("派生类的析构函数被调用") << endl;
  34. }
  35. private:
  36. double m_dHeight;
  37. double m_dWeight;
  38. };
  39. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
  40. {
  41. Person* pPersonObj = new Man;
  42. delete pPersonObj;
  43. return 1;
  44. }
上述代码的执行结果如下:
大家可能注意到了,上述代码中基类和派生类的析构函数都采用虚析构函数,而在_tmain函数中的调用方式也采用了Person* pPersonObj = new Man这种多态调用方式。当delete pPersonObj被执行来释放派生类对象的时候,实际上调用的是派生类对象的虚析构函数,而派生类对象的虚析构函数会调用基类的析构函数,这样就能将派生类对象完美的析构,如果这里不采用虚析构函数,会是什么结果呢?
 
  • #include <tchar.h>
  • #include <iostream>
  • using namespace std;
  • class Person
  • {
  • public:
  • Person()
  • {
  • cout << _T("基类的构造函数被调用") << endl;
  • }
  • virtual void Height()
  • {
  • cout << _T("人类具有身高属性") << endl;
  • }
  • ~Person()
  • {
  • cout << _T("基类的析构函数被调用") << endl;
  • }
  • };
  • class Man : public Person
  • {
  • public:
  • Man()
  • {
  • cout << _T("派生类的构造函数被调用") << endl;
  • }
  • virtual void Height()
  • {
  • cout << _T("男人具有身高属性") << endl;
  • }
  • virtual ~Man()
  • {
  • cout << _T("派生类的析构函数被调用") << endl;
  • }
  • private:
  • double m_dHeight;
  • double m_dWeight;
  • };
  • int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
  • {
  • Person* pPersonObj = new Man;
  • delete pPersonObj;
  • return 1;
  • }
上述代码执行结果如下:
 
我们可以看到,当delete pPersonObj被执行的时候,只调用了基类的析构函数,并没有调用派生类的析构函数,所以这个对象的派生部分的内存并没有被释放,从而造成内存泄露。
所以:当基类中包含有虚函数的时候,析构函数一定要写成虚析构函数,否则会造成内存泄露。
为什么一定要这么做呢?我们在第三篇的内容里寻找答案。
 
第三篇:自己打算写,待续。。。

C++虚函数解析(转载)的更多相关文章

  1. 构造函数为什么不能是虚函数 ( 转载自C/C++程序员之家)

    从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的.问题出来了,如果构造函数是虚的,就需要通过 vtable来调用, ...

  2. C++中的虚函数解析[The explanation for virtual function of CPlusPlus]

    1.什么是虚函数?                                                                                            ...

  3. 【转】C++虚函数解析

    本文转自陈皓大叔(左耳朵耗子)的博客www.coolshell.com. 文章是很久之前所写,去年还在写C++时有幸拜读,现在想起来还是相当有价值一转的,如果有一定C++基础(特别是读过<深度探 ...

  4. C++ 虚函数表解析(比较清楚,还可打印虚函数地址)

    C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父 ...

  5. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  6. [转载]C++虚函数浅析

    原文:http://glgjing.github.io/blog/2015/01/03/c-plus-plus-xu-han-shu-qian-xi/ 感谢:单刀土豆 C++虚函数浅析 JAN 3RD ...

  7. C++析构函数定义为虚函数(转载)

    转载:http://blog.csdn.net/alane1986/article/details/6902233 析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数.如果析构函数不是虚函 ...

  8. (C/C++学习)5.C++中的虚继承-虚函数-多态解析

    说明:在C++学习的过程中,虚继承-虚函数经常是初学者容易产生误解的两个概念,它们与C++中多态形成的关系,也是很多初学者经常产生困惑的地方,这篇文章将依次分别对三者进行解析,并讲述其之间的联系与不同 ...

  9. C++虚函数及虚函数表解析

    一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Functio ...

随机推荐

  1. 监听内容变化 TextWatcher @功能

    监听多个TextView的内容变化 使用示例 TextWatcherUtils.addTextChangedListener(isAllNotEmpty -> btnLogin.setEnabl ...

  2. 使用微软提供的Office Online实现Office文档的在线查看,编辑等功能

    使用微软提供的Office Online平台只需要一个网址即可在线查看Xls,doc,PPT等文档 http://view.officeapps.live.com/op/view.aspx?src=要 ...

  3. (C++)已知String类的定义,实现其函数体

    CString类的定义如下: class CMyString{ public: CMyString(const char* pData=NULL); CMyString(const CMyString ...

  4. C++ 生成

    1.重新生成,会导致所有cpp文件重新编译,然后连接. 2.使用生成,只会对需要重新编译的cpp文件,进行编译. a.修改cpp文件方法实现,只需要重新编译该cpp文件 b.修改h文件的接口部分,包含 ...

  5. GNU Make - 内部变量的赋值和改变

    1. 赋值和显示 采用$(info $(variable_name))显示内部变量 eg: FOO=bar $(info $(FOO)) 运行结果: #: make bar 2. 从命令行外部改变 B ...

  6. ZOJ 3630 Information 强连通

    题意:n m表示n个节点,m条边,下面m行a b 表示a-b点有一条有向边 题目:给定有向图,删去一个点后,可以求出该图中强连通分量中最大的点数 问:删去某点后,最大点数 最小是多少 思路:枚举删点, ...

  7. es5 - array - pop

    /** * 描述:该pop()方法从数组中删除最后一个元素并返回该元素.此方法更改数组的长度. * 语法:arr.pop() * 返回:从数组删除元素,如果为undefined则返回空该,pop方法从 ...

  8. Linux 监测磁盘常用的工具sar iostat vmstat

    Linux 检测内存常用的工具sar iostat vmstat #每秒刷新一次显示2次 sar -d 1 2 iostat -kx 1 2 vmstat -d 1 2 磁盘统计信息解释 tps 每秒 ...

  9. om.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException

    Error:Execution failed for task ':app:dexDebug'.> com.android.ide.common.process.ProcessException ...

  10. eclipse maven Cannot change version of project facet Dynamic web module to 3.0

      eclipse maven Cannot change version of project facet Dynamic web module to 3.0 (eclipse 修改maven项目的 ...