Effective C++读书笔记——item9(不要在构造,析构函数中调用虚拟函数)

server/2025/1/8 19:51:15/

核心问题阐述

在 C++ 中,不应在构造(construction)或析构(destruction)期间调用虚拟函数(virtual functions),因为这样的调用不会按预期工作,会引发意想不到的问题,甚至导致未定义行为和调试难题。这一情况在从 Java、C# 等语言转过来的程序员使用 C++ 时也需重点关注。

示例及原理分析

  1. 示例情况
    以模拟股票交易的类继承体系为例,包含基类Transaction以及派生类BuyTransactionSellTransaction等,在Transaction的构造函数中调用了纯虚函数logTransaction,当创建BuyTransaction对象时,实际调用的logTransaction版本是Transaction中的那个,而非BuyTransaction中的版本。这是因为基类构造期间,虚拟函数不会向下匹配到派生类,此时对象的行为就好像它只是基类型。
    class Transaction {                               // base class for all
    public:                                           // transactionsTransaction();virtual void logTransaction() const = 0;        // make type-dependent// log entry...
    };Transaction::Transaction()                        // implementation of
    {                                                 // base class ctor...logTransaction();                               // as final action, log this
    }                                                 // transactionclass BuyTransaction: public Transaction {        // derived class
    public:virtual void logTransaction() const;            // how to log trans-// actions of this type...
    };class SellTransaction: public Transaction {       // derived class
    public:virtual void logTransaction() const;             // how to log trans-// actions of this type...
    };
  2. 原理剖析
    基类构造函数先于派生类构造函数执行,在基类构造时,派生类数据成员还未初始化,若虚拟函数调用能向下匹配到派生类,而派生类函数往往涉及未初始化的局部数据成员,就会产生未定义行为。并且在派生类对象的基类构造期间,对象类型被视为基类类型,像涉及运行时类型信息的dynamic_cast等语言构件也按此看待对象。析构过程同理,进入基类析构函数时,对象就成为基类对象,相关语言构件也按此认定。

隐藏问题及检测难度

如果将共通的初始化代码(包含对虚拟函数的调用)放入私有非虚拟初始化函数中,问题会更隐蔽,可能躲过编译器和连接程序的检查,导致程序运行出现异常(比如调用纯虚函数时程序会异常中止,若是非纯虚函数且有实现则会调用错误版本继续运行)。

class Transaction {
public:Transaction(){ init(); }                                     // call to non-virtual...virtual void logTransaction() const = 0;...private:void init(){...logTransaction();                             // ...that calls a virtual!}
};

解决办法

class Transaction {
public:explicit Transaction(const std::string& logInfo);void logTransaction(const std::string& logInfo) const;   // now a non-// virtual func...
};Transaction::Transaction(const std::string& logInfo)
{...logTransaction(logInfo);                                 // now a non-
}                                                          // virtual callclass BuyTransaction: public Transaction {
public:BuyTransaction( parameters ): Transaction(createLogString( parameters ))              // pass log info{ ... }                                                  // to base class...                                                     // constructorprivate:static std::string createLogString( parameters );
};

Transaction中的logTransaction转变为非虚拟函数,让派生类构造函数将必要的日志信息传递给基类构造函数,比如通过派生类中创建辅助的静态函数生成要传递的信息,避免触及仍未初始化的数据成员,以此来补偿不能在基类构造过程中使用虚拟函数向下匹配的情况。

要点总结

在构造或析构期间不要调用虚拟函数,因为这类调用不会转到比当前执行的构造函数或析构函数所属的类更深层的派生类中去。


http://www.ppmy.cn/server/156226.html

相关文章

VSCode报错Module ‘“xx.vue“‘ has no default export.Vetur(1192)

在开发vue3项目引入自定义组件的时候,代码中总是会出现如下图中的错误: 错误提示中可以看到关键字Vetur Vetur是一个vscode插件,用于为.vue单文件组件提供代码高亮以及语法支持,但他只适用于对vue2语法的高亮提示,对…

Spring boot实现图片上传和下载

在pom.xml中添加依赖&#xff1a; <dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId> </dependency> 在controller中实现上传和下载 上传图片&#xff1a; 图片的base64格式为字符串&#xff0c;以…

26考研资料分享 百度网盘

26考研资料分享考研资料合集 百度网盘&#xff08;仅供参考学习&#xff09; 基础班&#xff1a; 通过网盘分享的文件&#xff1a;2026【考研英语】等3个文件 链接: https://pan.baidu.com/s/1Q6rvKop3sWiL9zBHs87kAQ?pwd5qnn 提取码: 5qnn --来自百度网盘超级会员v3的分享…

数据结构C语言描述9(图文结合)--二叉树和特殊书的概念,二叉树“最傻瓜式创建”与前中后序的“递归”与“非递归遍历”

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

Jmeter-性能测试工具的安装教程

一、Jmeter是什么&#xff1f; JMeter是一个开源的Java应用程序&#xff0c;由Apache软件基金会开发和维护&#xff0c;可用于性能测试、压力测试、接口测试等。 性能测试的定义 性能测试是通过自动化工具模拟多种负载条件&#xff08;包括正常、峰值和异常负载&#xff09;&am…

面向对象分析与设计Python版 建模工具UML

文章目录 一、建模与模型二、统一建模语言 一、建模与模型 建模与模型 建模 modeling&#xff1a; 把不太理解的东西和一些已经较为理解、且十分类似的东西做比较&#xff0c;可以对这些不太理解的东西产生更深刻的理解&#xff0c;叫做建模重要的研发成果常常产自类比 模型 …

flink cdc oceanbase(binlog模式)

接上文&#xff1a;一文说清flink从编码到部署上线 环境&#xff1a;①操作系统&#xff1a;阿里龙蜥 7.9&#xff08;平替CentOS7.9&#xff09;&#xff1b;②CPU&#xff1a;x86&#xff1b;③用户&#xff1a;root。 预研初衷&#xff1a;现在很多项目有国产化的要求&#…

STM32学习之TIM输出比较OC 以PWM驱动外设(LED/舵机/直流电机)

智能车、机器人等需要PWM波驱动电机&#xff0c;输出比较功能就干这个的&#xff0c;它主要用来输出PWM波形 OC&#xff08;Output Compare&#xff09;输出比较 &#xff08;另外还有IC全称是input capture输入捕获&#xff0c;CC全称是 capture / compare 表示 输入捕获和输…