【转】C++箴言:避免构造或析构函数中调用虚函数

38.XDMA寄存器详解2-H2C、C2H通道寄存器组剖析 目录 1.上节回顾 2. H2C寄存器剖析 2.1 H2C通道标识寄存器(0x00) 2.2 H2C通道控制寄存器(0x04) 2.3 H2C通道状态寄存器(0x40) 2.4 H2C通道完成描述符数量寄存器(0x48) 2.5 H2C通道对齐寄存器(0x4C) 2.6 H2C通道查询模式回写地址寄存器(0x88、0x8C) 2.7 H2C通道中断掩码寄存器(0x90)​ 2.8 H2C通道性能监视控制寄存器(0xC0) 2.9 H2C通道周期计数性能寄存器(0xC4、0xC8) 2 阅读详情

转载:http://www.china-b.com/kaoshi/kjsj/20090317/942163_1.html

 

如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉。但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼。

正文

我想以重复本文的主题开篇:不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已。如果你以前是一个Java或者C#程序员,请密切注意本节的内容-这正是C++与其它语言的大区别之一。

假设你有一个为股票交易建模的类层次结构,例如买单,卖单,等等。为该类交易建立审计系统是非常重要的,这样的话,每当创建一个交易对象,在审计登录项上就生成一个适当的入口项。这看上去不失为一种解决该问题的合理方法:

class Transaction {// 所有交易的基类
public:
Transaction();
virtual void logTransaction() const = 0;//建立依赖于具体交易类型的登录项
。。。
};
Transaction::Transaction() //实现基类的构造函数
{
。。。
logTransaction(); //最后,登录该交易
}
class BuyTransaction: public Transaction {
// 派生类
public:
virtual void logTransaction() const; //怎样实现这种类型交易的登录?
。。。
};
class SellTransaction: public Transaction {
//派生类
public:
virtual void logTransaction() const; //怎样实现这种类型交易的登录?
。。。
};
现在,请分析执行下列代码调用时所发生的事情:

BuyTransaction b;  构造顺序:Transaction() --> BuyTransaction()
很 明显,一个BuyTransaction类构造器被调用。但是,首先调用的是Transaction类的构造器-派生类对象的基类部分是在派生类部分之前 被构造的。Transaction构造器的最后一行调用了虚函数logTransaction,但是奇怪的事情正是在此发生的。被调用函数 logTransaction的版本是Transaction中的那个,而不是BuyTransaction中的那个-即使现在产生的对象的类型是 BuyTransaction,情况也是如此。在基类的构造过程中,虚函数调用从不会被传递到派生类中。代之的是,派生类对象表现出来的行为好象其本身就 是基类型。不规范地说,在基类的构造过程中,虚函数并没有被"构造"。

对上面这种看上去有点违背直觉的行为可以用一个理由来解释-因为基 类构造器是在派生类之前执行的,所以在基类构造器运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类,派生 类对象当然可以参照引用局部的数据成员,但是这些数据成员其时尚未被初始化。这将会导致无休止的未定义行为和彻夜的代码调试。沿类层次往下调用尚未初始化 的对象的某些部分本来就是危险的,所以C++干脆不让你这样做。

事实上还有比这更具基本的要求。在派生类对象的基类对象构造过程中,该类 的类型是基类类型。不仅虚函数依赖于基类,而且使用运行时刻信息的语言的相应部分(例如,dynamic_cast(参见Item 27)和typeid)也把该对象当基类类型对待。在我们的示例中,当Transaction的构造器正运行以初始化BuyTransaction对象的 基类部分时,该对象是Transaction类型。在C++编程中处处都这样处理,这样做很有意义:在基类对象的初始化中,派生类对象 BuyTransaction相关部分并未被初始化,所以其时把这些部分当作根本不存在是最安全的。 在一个派生类对象的构造器开始执行之前,它不会成为一个派生类对象的。


析构顺序: BuyTransaction () --> Transaction ()

在对象的析构期间,存在与上面同样的逻辑。一旦一个派生类的析构器 运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类 对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。

在上面的示例代码中,Transaction构造器 直接调用了一个虚函数-这明显地破坏了本文所强调的原则。这种破坏性非常容易觉察,一些编译器对此发出警告(注意:另外一些编译器并不给出警告,请参考 Item 53有关警告的讨论)。即使没有给出警告,该问题在代码运行时刻也是相当明显的,因为函数logTransaction是类Transaction中的纯 虚函数。除非该函数被定义了(可能性不太大,但确实存在这种情况-参见Item 34),否则程序不会进行链接:链接器没法找到Transaction::logTransaction的必需的实现代码。

在类的构造或 者析构函数中进行虚函数调用并非总是那么容易被发现。如果Transaction类有多个构造器且其中每个必须执行一些相同的任务,也许只有优秀的软件工 程师才能够避免代码的重复,这可以通过把相同的初始化代码(包括调用logTransaction)放到一个私有的且非虚的初始化函数中实现,譬如下面的 init:

class Transaction {
public:
Transaction()
{ init(); } //调用非虚函数。。。
virtual void logTransaction() const = 0;
。。。
private:
void init()
{
。。。
logTransaction(); //注意这里调用了虚函数
}
};

这 段代码从概念上看与前面的版本一样,但是却更具有潜在的危险性,因为典型情况下,该代码会被成功地编译与链接。在这种情况下,因为 logTransaction是Transaction类中的纯虚函数,绝大多数的运行时刻系统会在该纯虚函数被调用时(典型地是通过发送一个带有调用该 函数意义的消息实现)流产掉程序。然而,如果logTransaction是一个"正常的"虚函数(也就是,不是纯虚的),并在Transaction中 有它的实现部分,该代码段将被调用而且程序会顺利地运行一段时间,这让你考虑为什么在一个派生类对象被创建时调用了logTransaction的错误版 本。唯一避免该问题的办法是确保没有任何一个构造器或者析构器在正被产生或毁坏的对象上调用了虚函数,而且所有其调用的函数都要遵循同样的约束。

但是,每当有一个对象在Transaction类层次结构中产生时,如何保证调用的是logTransaction的正确版本呢?很明显,从Transaction的构造器中调用对象上的虚函数是错误的做法。

有 几种不同的办法可以解决这个问题。一种办法就是在Transaction中把函数logTransaction改变为一个非虚函数,然后要求派生子类的构 造器要把必要的登录信息传递给Transaction的构造器。如此以来,上面的函数就能够安全地调用非虚函数logTransaction了。如下所 示:

class Transaction {
public:
explicit Transaction(const std::string logInfo);
void logTransaction(const std::string logInfo) const;//现在是一个非虚函数
。。。
};

Transaction::Transaction(const std::string logInfo)
{
。。。
logTransaction(logInfo);// 现在调用的是一个非虚函数
}

class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
:Transaction(createLogString(parameters)) { 。。。 } //把登录信息传送给基类的构造函数
。。。
private:
static std::string createLogString( parameters );
};
换句话说,既然在基类的构造函数中不能沿着类的继承层次往下调用虚函数,你可以通过在派生类中沿着类的层次结构把必要的构造信息传递到基类的构造器中来补偿这一点。

在 这个例子中,请注意BuyTransaction中私有静态函数createLogString的使用方法。通过使用帮助函数来创建一个值并把它传递到基 类构造器中,这种方式比起在成员初始化列表中实现基类所需的操作要更方便和更具有可读性。这里我们把该函数创建为static型,这对于偶尔参照引用一下 刚产生的BuyTransaction对象的尚未初始化的数据成员是没有危险的。这一点很重要,因为那些数据成员还处于一种未定义的状态中,这一事实解释 了为什么在基类的构造或者析构函数中对于虚函数的调用不能首先传递到派生子类中去。

结论

不要在类的构造或者析构过程中调用虚函数,因为这样的调用永远不会沿类继承树往下传递到子类中去。

BW 技术 InfoObject 初始化,一次把 0LANGU、0CURRENCY、0IOBJNM 这些底座对象讲透 摘要:BW/4HANA和S/4HANA嵌入式分析中最容易被忽视的关键技术InfoObject(如0LANGU、0UNIT等)是系统运行的基础骨架。这些对象虽不直接涉及业务,但若未正确激活会导致多维分析、查询运行等核心功能异常。BW/4HANA中应通过任务清单SAP_BW4_SETUP_SIMPLE或SAP_BW4_SET_OPERATION_SCOPE激活,而非旧事务码RSTCO_ADMIN。S/4HANA嵌入式分析仍需依赖这些底层BW组件,配置需参考SAP Note 2636754。项目实践中需特别关注这 阅读详情

相关推荐

影刀RPA进阶:Excel多条件筛选与数据提取实战

本文详细介绍了如何利用影刀RPA实现Excel多条件筛选与数据提取的自动化。通过拆解文本、数值、日期及颜色筛选等核心指令,并结合实战案例,指导用户构建稳健的销售数据提取机器人,从而高效处理复杂数据报表,提升工作效率。

onion的博客 527

TypeScript之断言和类型

TypeScript 断言 3.1 类型断言 有时候你会遇到这样的情况,你会⽐ TypeScript 更了解某个值的详细信息。通常这会发⽣在你清楚地知道⼀个实体具有⽐它现有类型更确切的类型。 通过类型断⾔这种⽅式可以告诉编译器,“相信我,我知道⾃⼰在⼲什么”。类型断⾔好⽐其他语⾔⾥的类型转换,但是不进⾏特殊的数据检查和解构。它没有运⾏时的影响,只是在编译阶段起作⽤。 1. "尖括号"语法 let someValue: any = "this is a string"; let strLength: numb

学技术,用技术,学以致用 1180

Databricks Genie:语义编译器架构与企业级智能解析实践

语义层是现代数据平台实现自然语言查询的核心基础,其本质是将业务术语映射为可执行、可治理的数据契约。原理上依赖结构化元数据、值字典与数据血缘三支柱协同,通过分层AI代理(如Intent Parsing、Retriever、Verifier)实现意图识别→语义对齐→安全执行的闭环。技术价值在于降低SQL使用门槛的同时保障审计性、一致性和权限可控性,适用于销售运营、财务分析等一线业务场景。本文深入解析Databricks Genie作为典型语义编译器的落地逻辑,涵盖Unity Catalog知识注入、七层Agen

weixin_29053695的博客 337

C++箴言:避免析构函数调用虚函数

如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉。但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼。   正文   我想以重复本文的主题开篇:不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已。如果你以前是一个Java或者C#程序员,请密切注意本节的...

DLQFHL的博客 302

贴片二极管SOD封装详解:SOD-123/323/523尺寸焊盘设计要点

表面贴装技术中,二极管封装尺寸直接决定PCB布局与焊接可靠性。SOD封装作为小外形二极管的主流形态,其SOD-123、SOD-323、SOD-523三种规格分别对应不同的功耗与占板面积权衡。理解封装尺寸、焊盘设计计算逻辑和钢网开窗原理,是避免立碑、虚焊等工艺缺陷的关键。在可穿戴设备、电源保护等空间受限场景中,合理选择封装不仅能优化散热路径,还能提升量产良率。本文从焊盘计算、工艺要点到选型策略,全面解析SOD封装的工程实践。

weixin_42520780的博客 293

微信小程序 - {errCode: 10008, errno: 1500104, errMsg: “writeBLECharacteristicValue:fail write characteris

微信小程序errCode: 10008, errno: 1500104, errMsg: "writeBLECharacteristicValue:fail write characteristics error. 10008, errMsg=The value's length is invalid.",uni-app微信小程序报错,uniApp小程序调用蓝牙api报错,向低功耗蓝牙设备的某个特征写入数据时错误,UniApp微信小程序蓝牙API调用报错10008解决方案,蓝牙设备进行通信时,向设备写入数据

🏆 前端领域优质创作者 4428

构造/析构函数中不要调用虚函数

看到一句话:构造函数/析构函数中不要调用虚函数。        在建立一个对象时,会依次调用父构造函数->子构造函数,同样,在析构时也会逆过来调用子析构函数->父析构函数。那么在调用的时候,如果析构函数中对于虚函数还执行虚机制,就有可能已经执行过一个子对象的析构函数,又去调用子对象的函数,这样会很危险。所以在虚析构函数中,对于虚函数,只会执行目前最外一级的那个函数。        构造函数执行

liuxuejiang158的专栏 2631

C++箴言:避免构造或析构函数中调用虚函数

如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉。但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼。   正文   我想以重复本文的主题开篇:不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已。如果你以前是一个Java或者C#程序员,请密切注

u012719556的专栏 741

uniapp [全端兼容] - 实现内网使用天地图离线地图,详解项目加载天地图的离线地图服务器部署与使用教程示例源码,Uniapp如何实现天地图的离线加载部署在内网访问(兼容H5、微信小程序、App)

uniapp天地图如何离线访问,无需网络调用天地图API,天地图离线地图,天地图——内网使用离线地图,uniapp中加载天地图的离线地图,uniAPP实现天地图离线访问,在内网中无法直接使用外网天地图资源怎么办,在uni-app中加载地图 -使用天地图的瓦片数据。UNIAPP天地图离线怎么使用,内网部署天地图,天地图离线瓦片,部分瓦片数据,天地图如何离线,下载天地图离线服务,并用Uniapp+天地图离线部署,天地图私有化部署,实现离线加载天地图,Java+uniapp天地图离线开发,uniapp项目中无网络

🏆 前端领域优质创作者 4900

如何通过单步调试的方法,找到 SAP Gateway 到底把 Fiori 应用发送的 OData 请求,投递到哪一台 ABAP 后台服务器了?

这是一个 Count 请求,得到服务器响应的数据是 204,这个 204 会以数字的形式,显示在 Fiori Launchpad tile 上。那如果当前 Gateway 系统可以连接到多台 ABAP 后台服务器,比如下图所示,可以连接 SAP S/4HANA,ERP 或者 SAP CRM,我怎么知道 SAP UI5 应用上显示的数据,到底来自哪一台 ABAP 后台服务器呢?,就是上图 ABAP 调试器里断点停留的位置,即 SAP Frontend 服务器,通过 RFC 将 OData 请求投递给。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 640

C++箴言:避免析构函数调用虚函数 转载

如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉。但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼。 正文   我想以重复本文的主题开篇:不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已。如果你以前是一个Java或者C#程序员,请密切

蓝天的专栏 1051

DCMTK编译

dcmtk-3.6.4 使用Unicode?dcmtk的编译选项里没有关于Unicode的选项,OFString的接口里明确使用了OFString(char*)作为构造函数参数,所以,我们可以假设不能够自己擅自在CMakeList.txt里增加一行 add_definitions(-DUNICODE -D_UNICODE) 来强制使用Unicode编码。 亲测:加了之后编译不过 void...

C++ 爱好者 make_it_simple888 1733

C++箴言:避免构造或析构函数中调用虚函数 zz

在构造函数/析构函数中调用虚函数的时候并不是你所想要的那个函数.... C++箴言:避免构造或析构函数中调用虚函数 http://dev.yesky.com/365/2028365.shtml

huobengle 111

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

C++箴言:绝不在构造或析构期调用虚函数

C++ 构造函数 析构函数 与虚函数的关系

ruixj的专栏 1002

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

C++箴言:绝不在构造或析构期调用虚函数(转)

你不应该在构造或析构期间调用虚函数,因为这样的调用不会如你想象那样工作,而且它们做的事情保证会让你很郁闷。如果你转为 Java 或 C# 程序员,也请你密切关注本文,因为在 C++ 急转弯的地方,那些语言也紧急转了一个弯。   假设你有一套模拟股票处理的类层次结构,例如,购入流程,出售流程等。对这样的处理来说可以核查是非常重要的,所以随时会创建一个 Transaction 对象,将这个创建记录在核

迷途 1077

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: 【转】MFC窗口销毁过程
下一篇: Windows Mobile 窗口全屏
code09
博客等级 码龄18年 31粉丝 56原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值