C++虚函数浅析

  • 虚函数的工作原理:
  • 虚析构函数:
  • C++中的静态联编和动态联编

学习过C++的都知道可以通过虚函数实现多态。在基类中定义一个虚函数,在派生类中可以重写这个虚函数,实现派生类自己的特性。

虚函数的工作原理:

C++规定了函数名参数返回值,没有规定实现,可以根据需要自行实现内容。通常编译器处理虚函数的方法是给每个对象添加一个隐藏成员。该成员保存了一个指向函数地址的数组指针,这个数组指针也就是虚函数表。虚函数表中保存了对象中所有虚函数的地址(包括继承的基类的虚函数地址),如果派生类多重继承就会存在多个虚函数表,派生类本身的虚函数表会出现在继承顺序第一个基类后面,下面举例演示一下:

class a{
public:virtual void base_a1();virtual void base_a2();
]
class b{
public:virtual void base_b1();virtual void base_b2();
]
class c{
public:virtual void base_c1();virtual void base_c2();
]
class d:public a,public b,public c{virtual void derive_d1();virtual void derive_d2();
}
int main(){d derive_d;
}

上面代码中定义derive_d的虚函数表结构如下图:

在对象所占内存的开始位置存在隐藏成员指针a、指针b、指针c,他们分别指向对应的虚函数表,对象自己的虚函数表在第一个继承的基类的后面。

class d:public a,public b,public c{void base_b2();virtual void derive_d1();virtual void derive_d2();
}

如果派生类重写了基类的函数(方法),那么在上图base_b2处的地址将变成指向派生类实现base_b2方法的地址。此时用派生类的对象在调用base_b2方法时调用的就是派生类重写的base_b2方法。

虚析构函数:

为什么基类的析构函数必须声明称虚函数呢?大家可能都知道,基类的析构函数不声明成虚函数时在释放时候有可能会导致子类的析构函数调用不到的情况。

class Base{
public:Base(){qDebug()<<"base";}~Base(){qDebug()<<"~base";}
};
class Derive:public Base{
public:Derive(){qDebug()<<"Derive";}~Derive(){qDebug()<<"~Derive";}
};
int main(){Base *p=new Derive();delete(p);
}
输出:
base
Derive
~base

上面代码执行情况就会先调用基类的构造函数然后调用派生类的构造函数,然后调用基类的析构函数。这就导致了内存泄露,为派生类分配了内存但是并没有释放。如果main函数像下面这样实现就不会出现这种情况:

int main(){Derive *p=new Derive;delete(p);
}
输出:
base
Derive
~Derive
~base

因为派生类的析构函数会调用基类的析构函数。虽然这种方法可以避免内存泄露,但是有时候某种情况下必须定义父类指针去指向一个子类的对象所以还是存在风险。只要将Base类如下声明就可以完全避免这个问题。

class Base{
public:virtual ~Base();
}
输出:
base
Derive
~Derive
~base

这样无论你是定义子类指针去指向一个子类对象还是用父类定义指针去指向子类对象都不会有内存泄露的问题。
到这里可能大家都知道,但是作者在这里提出一个疑问,为什么父类声明成虚构造函数就能在delete时候既调用到子类的析构函数又能调用到父类的析构函数呢?这就不得不提一下C++中的静态联编和动态联编了。

C++中的静态联编和动态联编

在提到动态和静态的时候我不禁想起了C语言中的动态库和静态库,其实动态联编和静态联编的区别与动态库和静态库的区别十分相似。
程序在调用函数时候调用哪个函数,执行哪段代码是由编译器负责的,在C语言中每个函数名不同调用起来就十分简单,但是C++中可以重载的缘故,编译器必须确定什么时候执行哪个函数调用哪段代码,在编译过程中可以确定调用哪段代码的被称为静态联编(早期联编)。由于C++中存在虚函数,使用哪个函数调用哪段代码在编译时并不能确定就像之前例子中说的定义父类的指针去指向一个子类的对象,这时编译器无法做出正确的判断,不知道是该调用父类的析构函数还是子类的析构函数。编译器必须在程序运行时调用正确的虚函数代码,这就被称为动态联编。动态联编和虚函数是息息相关的(虚函数采用动态联编非虚函数采用静态联编)。动态联编效率会低于静态联编,所以不要声明没有必要的虚函数,这样会导致效率降低。
介绍完静态联编和动态联编,我们回到上面的问题:

为什么父类声明成虚构造函数就能在delete时候既调用到子类的析构函数又能调用到父类的析构函数呢?

因为在定义为非虚析构函数时会采用静态联编,调用析构函数时因为是静态联编,在编译时会按照指针定义的类型也就是父类的类型,所以在调用哪个析构函数做选择时会选择父类的析构函数;但是在将基类的析构函数定义为虚析构函数后,就会采用动态联编,调用析构函数时会按照指针实际所指向的类型,也就是子类的类型,调用的自然就是子类的析构函数。上面说到过子类的虚构函数会调用父类的析构函数,所以父类子类的析构函数都被调用到了。

注意:

  1. 内联函数不能是虚函数,因为内联函数是在编译阶段展开的,而虚函数是在运行时动态调用的,编译时无法展开。
  2. 构造函数不能是虚函数,因为构造函数是在创建对象时候调用的,在创建派生类对象时会先调用基类的构造函数再调用派生类的构造函数。构造函数声明成虚函数是毫无意义的。
  3. 静态成员函数不能是虚函数,静态函数相当于普通函数和类、实例并没有关系所以也不存在多态,而虚函数是一种特殊的成员函数用来实现运行时多态的。所以静态成员函数声明成虚函数毫无意义。

以上内容均是作者查阅资料加自己理解,如有疑问,望读者不吝赐教,谢谢!
参考文献《C++ primer plus》(第六版)中文版

C++虚函数、虚析构函数浅析相关推荐

  1. 构造函数不可以是虚函数;析构函数可以是虚函数,也可以是纯虚函数。

    构造函数不可以是虚函数:析构函数可以是虚函数,也可以是纯虚函数. 一:构造函数不能声明为虚函数的原因 1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的. 而在构造 ...

  2. 为什么构造函数不能声明为虚函数,析构函数可以,构造函数中为什么不能调用虚函数?

    为什么构造函数不能声明为虚函数,析构函数可以,构造函数中为什么不能调用虚函数 构造函数中为什么不能调用虚函数? 第一个理由是概念上的 第二个理由是机械上的. 构造函数不能声明为虚函数的原因是 1 构造 ...

  3. 38.【C++ 虚函数 纯虚函数 虚基类 (最全详解)】

    虚函数.虚基类.纯虚函数 (一).虚函数 1.什么是虚函数: 2.虚函数的格式: 3.关于虚函数的注意事项: 4.虚函数的作用: 5.虚函数访问格式 6.虚函数的各种疑难杂症 [当指针是基类.但虚函数 ...

  4. 虚函数、纯虚函数、虚函数与析构函数

    一.虚函数 只有用virtual声明类的成员函数,使之成为虚函数,不能将类外的普通函数声明为虚函数.因为虚函数的作用是允许在派生类中对基类的虚函数重新定义.所以虚函数只能用于类的继承层次结构中. 一个 ...

  5. 构造函数不可以声明为虚函数,析构函数可以声明为虚函数

    构造函数不能声明为虚函数,而析构函数可以声明为虚函数,在有的情景下析构函数必须声明为虚函数.  不建议在构造函数和析构函数里调用虚函数. 构造函数不能声明为虚函数的原因? 构造一个对象时,必须知道对象 ...

  6. 【C++】多态 - 虚函数/虚析构函数以及虚函数表

    什么是多态: 指不同对象收到相同消息时或相同对象收到不同消息时产生不同的动作. 这里先说下为什么会用到虚函数: 以下面的程序为例: 这个程序中,Carp是Fish的继承类,而Carp中覆盖了Swim这 ...

  7. 图解C++虚函数 虚函数表

    图解C++虚函数 2016年07月02日 17:47:17 海枫 阅读数:5181 标签: 虚函数c++g++对象模型C++虚函数更多 个人分类: C/C++/linux 版权声明:本文为博主原创文章 ...

  8. c语言中虚函数和纯虚函数,虚函数和纯虚函数的区别是什么?

    虚函数和纯虚函数的区别:1.纯虚函数只有定义,没有实现:而虚函数既有定义,也有实现的代码.2.包含纯虚函数的类不能定义其对象,而包含虚函数的则可以. 相关推荐:<C++视频教程> 虚函数( ...

  9. 虚函数 虚继承 抽象类

    虚函数.纯虚函数.虚基类.抽象类.虚函数继承.虚继承 虚函数:虚函数是C++中用于实现多态(polymorphism)的机制.核心理念就是通过基类访问派生类定义的函数.是C++中多态性的一个重要体现, ...

  10. 虚函数 纯虚函数 虚基类说明

    原文:http://www.cnblogs.com/ms-frank/archive/2008/01/16/1041310.html 虚基类 在说明其作用前先看一段代码 [cpp] view plai ...

最新文章

  1. pygame 笔记-8 背景音乐子弹音效
  2. 露雨资源库(第一个.net2.0软件)二
  3. linux 根据进程号查看对应的进程
  4. perl怎么拷贝一个文件到另一个文件夹中或者怎么拷贝文件夹到另一个文件夹
  5. 信息学奥赛C++语言:最大数max(x,y,z)
  6. CVPR 2019 | 西北工业大学开源拥挤人群数据集生成工具,大幅提升算法精度
  7. 三、fs文件操作模块
  8. Flutter-ThemeData详解
  9. postgres 把一个表的值转成另一个表的字段名_Postgres索引那些事
  10. 超低代码拓荒记 | 工业互联网新边疆
  11. example datasets in sklearn
  12. Spring——基于注解的IOC配置常用注解
  13. ImportError:cannot import name 'distribute_covar_matrix_to_match_covariance_type'
  14. 计算机原理视频罗克露优酷,5IO系统-2中断-罗克露计算机组成原理课件(绝对与网上视频教程同步)...
  15. 马云的教、马云的会、马云的墓
  16. 华北赛区承办学校:太原工业学院
  17. kettle效率提升
  18. opencv图像仿射变换,cv2.warpAffine
  19. 华为鸿蒙系统是指芯片吗_华为要卖掉荣耀?假的!荣耀V40采用麒麟芯片和鸿蒙系统...
  20. linux 下 android手机驱动解决办法

热门文章

  1. java,jdk 分不清,是否免费,怎么选择合适的版本
  2. 数据泵linux,Linux操作系统上用数据泵导库
  3. 最新OneDrive获取WebDAV教程
  4. 贝叶斯概率(先验概率后验概率)
  5. excel 实现身份证号倒数第二位是奇数的显示男,偶数的显示女
  6. 大厂都重视插画设计,不会怎么办?
  7. 云鲸智能首次亮相上海进博会,引领行业发展新风向
  8. [掌握一个小项目]简单利用Java实现可输入计算器
  9. GitBook插件整理 - book.json配置
  10. Ubuntu虚拟机安装VMware Tools