刨析一下C++构造析构函数能不能声明为虚函数的背后机理?

先说结论:

构造函数不能声明为虚函数,析构函数可以声明为虚函数。

构造函数可以声明为虚函数吗?

虚函数表里都存了些什么东西?不是金,不是银,是对应类里声明为虚函数的成员地址。在编译期,每个类的虚函数表即被分配和生成。同一个类的所有实例对象都是共享这个虚函数表的,那么每个实例对象也就会隐含有一个成员指针变量专门用来存储虚函数表的地址。这个隐含的成员指针变量需要在实例对象初始化后才会指向虚函数表。

很显然,对象不能在没有初始化之前就知道自己对应的虚函数表在哪里,因此也不能在对象初始化之前调用访问虚函数表的内容(虚函数)。是不是在对象初始化之前就不能调用访问这些虚函数成员了?不是这个意思,这里说的不能仅限于通过虚函数表来访问(比如派生后的类实例对象通过父类的指针变量访问调用虚函数成员),也就是动态访问时才需要虚函数表的信息。不过,就算某个成员函数被声明为虚函数时,也可以通过类的静态特性合法访问的,这是无关痛痒的题外话,评价____。

对象的初始化就是依赖于构造函数的执行,首先是找到最上一层父类的构造函数并执行,然后逐层往下执行构造函数,直到执行完当前类(被实例化的类)的构造函数,这个过程不需要虚函数表的任何信息,在编译期就确定了所有需要的信息了。在构造函数执行之前,对象还无法确定自身的虚函数表在哪里,又怎么从虚函数表里查找对应的虚构造函数呢?如果把构造函数声明为虚函数,那么意义在哪儿呢?想来想去,可能费劲打造出一把刀刃能削铁如泥的好刀,却只是在需要敲钉子时,想起用这把刀的刀背。

所以,很明显构造函数不能声明为虚函数。

析构函数可以声明为虚函数吗?

先看下面的代码,析构函数不声明为虚函数时,

#include <iostream>
using namespace std;

class Base
{
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base
{
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

class Derived_again : public Derived
{
public:
    Derived_again() {
        cout << "Derived_again constructor" << endl;
    }
    ~Derived_again() {
        cout << "Derived_again destructor" << endl;
    }
};

int main()
{
    Base *obj = new Derived();
    cout << "----" << endl;
    delete obj;
    return 0;
}
</iostream>

看看编译后执行的结果(这里用的编译器是g++)

Base constructor
Derived constructor
Derived destructor
Base destructor

可以看到,需要调用的析构函数都调用了。

那么怎么去理解上面这段代码的执行逻辑呢?

delete obj;

销毁对象时,系统执行的逻辑有两种情况。

一种是,如果析构函数没有被声明为虚函数时,那么在指针obj指向的对象的虚函数表里,是找不到虚析构函数的。由于系统无法往下查找派生类的内容,而且变量obj被声明为某个类(上面代码对应的是Base)的指针类型,那么系统就转为调用这个类(Base)的成员析构函数,这里利用的是静态特性。

另一种是,如果析构函数被声明为虚函数时,先在指针obj指向的对象的虚函数表里,尝试寻找当前对象obj的虚析构函数,找到后执行它。对应代码,被实例化的类是Derived,对象obj的虚析构函数地址应该指向类Derived的成员析构函数。

上面两种情况中,找到第一个析构函数并执行后,会按照静态特性(也就是按照编译期生成的信息),逐层往上一层父类查找成员析构函数并执行,直到最上一层的父类的析构函数被执行完毕为止。

于是,第一种情况下,类Derived的成员析构函数被漏掉执行了,这会导致类Derived所申请的资源没有对应的析构函数来执行释放,内存泄漏发生地那么顺理成章。

可以看到,动态特性可以把事情玩得妥妥当当,面向对象的高级感可能就来自这里。

总结一下,很明显,析构函数可以声明为虚函数,但不是必须。某些情况下,也是必须的,比如,当类指针指向的是该类的子类实例时,析构函数必须声明为虚函数,以防止内存泄漏。

Original: https://www.cnblogs.com/englyf/p/16631774.html
Author: englyf八戒
Title: 刨析一下C++构造析构函数能不能声明为虚函数的背后机理?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/574070/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Nginx参数详解

    ​ 从配置文件到events之间的内容,主要会设置一些影响nginx服务器整体运行的配置命令。主要包括配置运行nginx服务器的用户(组)、允许生成的worker process数…

    Linux 2023年6月11日
    0100
  • 搭建zabbix 4.0

    安装zabbix的依赖包 下载zabbix源码包 数据库导入数据的命令格式:mysql ­u用户名 ­p密码 数据库名称 < 要导入的数据 此时的路径是在databases/…

    Linux 2023年6月8日
    0117
  • 02-MySQL关键字、Select语句执行顺序

    SQL关键字 1、分页 MySQL的分页关键词是 limit SELECT * FROM student LIMIT 2,6:查询学生表中的数据,从第三条开始,显示6条数据 2、分…

    Linux 2023年6月7日
    085
  • 【凸优化】1 仿射集,凸集,锥

    1. 仿射集 Affine Sets 1)定义 定义1:(x_1, x_2)为集合(C\subseteq \mathbb{R}^n)中的任意两点,如果穿过(x_1,x_2)的 直线…

    Linux 2023年6月7日
    092
  • python练习题:利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法

    方法一: 方法二: (此方法会有一个问题,当字符串仅仅是一个空格时’ ‘,会返回return s[1:0];虽然不会报错,但是会比较奇怪。测试了下,当s=&…

    Linux 2023年6月8日
    0109
  • Linux下使用压力测试工具stress

    首先解压安装包到/usr/local/src/下 mv stress-1.0.4.tar.gz /usr/local/src​tar -zxf stress-1.0.4.tar.g…

    Linux 2023年6月13日
    089
  • Windows 下日志保存至Linux rsyslog日志服务器

    一、 下载安装 通过https://www.rsyslog.com/windows-agent/windows-agent-download/下载客户端后,按照默认安装完成后即进行…

    Linux 2023年6月6日
    0100
  • 23种设计模式概要及易懂的例子

    创建型模式(共五种) 工厂方法模式:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 个人总结: 通过对象工厂灵活地生产多种对象 抽…

    Linux 2023年6月13日
    0102
  • k8s-简介

    Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统,K8s 作为缩写的结果来自计算”K”和”s”之间的八个…

    Linux 2023年6月13日
    086
  • windows下设置redis开机自启动

    windows: 在windows下安装目录下 打开命令窗口: redis-server.exe –service-install redis.windows.conf…

    Linux 2023年5月28日
    096
  • Docker如何镜像加速

    原文链接:https://www.zhoubotong.site/post/69.html在使用Docker 下载镜像时,如果不配置镜像加速,下载镜像会比较慢,因为国内从 Dock…

    Linux 2023年6月6日
    0138
  • 如何配置VLAN

    一、vlan的概念与作用 首先,在学习如何配置vlan时我们先要了解一下为什么要配置vlan?vlan在平常的工作中有什么作用? vlan:虚拟的划分网段 即虚拟网络,在平常的工作…

    Linux 2023年6月6日
    0152
  • MySQL之多表查询、Navicat及pymysql

    一、多表查询 1.1 数据准备 — 建表 create table dep( id int primary key auto_increment, name varchar(20…

    Linux 2023年6月14日
    0104
  • 剑指offer计划24( 数学中等)—java

    1.1、题目1 剑指 Offer 14- I. 剪绳子 1.2、解法 这几天的题都不在行。。。。 1.3、代码 class Solution { public int cuttin…

    Linux 2023年6月11日
    091
  • 关于 Promise 的一些简单理解

    一、ES6 中的 Promise 1、JS 如何解决 异步问题? (1)什么是 同步、异步?同步指的是 需要等待 前一个处理 完成,才会进行 下一个处理。异步指的是 不需要等待 前…

    Linux 2023年6月11日
    0115
  • 最新超详细Linux下LNMP环境搭建

    一、了解LNMP系统 当前两个主要应用的架构:LNMP和LAMP,都是指一组通常一起使用来运行动态网站或者服务器的自由软件名称。 LAMP的全称是:Linux + Apache +…

    Linux 2023年6月15日
    0145
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球