C++编译器默认构造函数合成机制分析

咱们一起学C++第一百零三篇:之默认构造函数与初始化清除的重要性 在C++的学习之路上,我们携手前行,不断探索这门语言的奥秘。此次,我们聚焦于默认构造函数以及初始化和清除在C++中的重要意义。希望通过深入剖析,能让大家清晰地理解这些概念,认识到它们在编写安全、高效C++程序中的关键作用,从而提升大家的编程水平,让我们一起在C++的知识海洋中畅游,共同成长,共同进步。 阅读详情


我们通常会说当生命一个 class 时,如果我们不为该 class 指定一个 constructor,那么编译器会替我们实现一个 connstructor,那么这种说法一定对吗?

事实上,这是不对的。这个不是我说的,是深入理解C++对象模型说的,原话是:


      C++新手一般有两个常见的误解:

  1. 任何 class 如果没有定义 default constructor,就会被合成出一个来。                                    
  2. 编译器合成出来的 default constructor 会明确设定 "class 内每一个 data member 的默认值。

首先解释第一点,只有四种情况才会导致 ”编译器必须为未声明 constructor 之 classes 合成一个 default constructor“。被合成出来的 constructor 只能满足编译器(而非程序)的需要。它们之所以能够完成任务,是借着 “调用 member object 或base class 的 default constructor” 或是 “为每一个 object 初始化其virtual function 机制或 virtual base class 机制”而完成。至于没有存在那四种情况,而又没有声明任何 constructor 的 classes,我们说它拥有的是 implicit trivial(浅薄而无能,没啥用的),它们实际上并不会被合成出来。


下面分别讨论上述四种合成 constructor 的情况:

情况一:一个 class 内含一个 member object,且其 member object 拥有 default constructor

class Foo { public: Foo(), Foo( int ) ... };
class Bar { public: Foo foo; char* str; };  //不是继承,是包含

void foo_bar(){
    Bar bar;   //Bar::foo 必须在此处初始化
               //译注:Bar::foo是一个member object,而其 class Foo 拥有default constructor,符合本小节主题
    if( str ) {  }  ...
}
此时 class Bar 内含一个 member object Foo,并且 Foo 有 default constructor Foo(),那么编译器就会为 Bar 合成一个 default constructor,看起来像这样:

//Bar 的 default constructor 可能会这样合成
//被 member foo 调用 class Foo 的 default constructor
inline
Bar::Bar(){
    //C++伪码
    foo.Foo::Foo();
}
再一次请你注意,被合成的 default constructor 只满足编译器的需要,而不是程序的需要,为了让这个程序片段能够正确执行,字符指针 str 也需要初始化,那么程序员可能会这么做:

Bar::Bar() { str = 0; }  //程序员定义的 default constructor
此时编译器还需要初始化 member object foo,但是由于 default constructor 已经被程序员明确定义出来了,编译器没办法合成第二个。编译器采取的行动是:“如果 class A 内含一个或一个以上的 member class object,那么 class A 的每一个 constructor 必须调用每一个 member classes 的 default constructor”,编译器会向用户程序员的 constructors 前面插入必要的 default constructor。插入后可能像这样:

//插入后的 default constructor 
//C++伪码
Bar::Bar(){
    foo.Foo::Foo();   //插入的 compiler code
    str = 0;            //explicit user code
}
如果有多个 class member objects 都要求初始化操作,将如何做呢?C++会按照 “member objects 在 class 中的声明次序“ 来调用各个 consructors。这一点由编译器完成。并且如果某个 member object 的 default constructor 被程序员定义过了,它照样会被按顺序调用。如:

//程序员对 Snow_White 类所写的 default constructor,显示初始化一个 member object
Snow_White::Snow_White() : sneezy( 1024){
    mumble = 2048
}

它会被扩张为:

Snow_White::Snow_White() : sneezy( 1024) {
    //插入 member class onject
    //调用其 constructor
    dopey.Dopey::Dopey();
    sneezy.Sneezy::Sneezy(1024);
    bashfun.Bashfun::Bashful();

    //explicit user code
    mumble = 2048;
}


情况二:带有 “default constructor” 的 base class 

类似的道理,如果一个没有任何 constructors 的 class 派生自一个 ”带有 default constructor“ 的 base class,那么这个 derived class 的 default constructor 会被是为 nontrivial,并且因此需要被合成出来,它将调用上一层 base classes 的 default constructor(根据它们的声明次序)。对一个后继派生的 class 而言,这个合成的 constructor 和一个 ”被明确提供的 default constructor” 其实没什么区别。

如果设计者提供多个 constructors(比如带 int 参数的 constructor),但其中都没有 default constructor 呢? 编译器会扩张现有的每一个 constructors,在前面插入 base class 的 default constructor。如果有成员 member object,同样按顺序调用。



情况三:带有一个 “virtual Function” 的 class

如果一个 class 声明或继承一个 virtual function,并且缺乏由用户声明的 constructors,编译器会详细记录合成一个 default constructor 的必要信息。如:

class Widget{
    public:
        virtual void filp() = 0;
        ...
}

void filp( const Widget& widget ) { widget.filp(); }

//假设 Bell 和 Whistle 都派生自 Widget
void foo(){
    Bell b;
    Whistle w;

    filp( b );
    filp( w );
}

下面两个扩张步骤在编译期间发生:

  1. 一个 virtual function table (在 cfront 中被称为 vtbl)会被编译器产生出来,内放 class 的 virtual functions 地址。
  2. 在每一个 class onject 中,一个额外的 pointer member(也就是 vptr)会被编译器合成出来,内含相关的 class vtbl 的地址。
此外,widget.filp() 的虚拟引发操作(virtual invocation)会被重新改写(实际上就是虚函数调用传入的 this 会变化),以使用 widget 的 vptr 和 vtbl 中的 filp() 条目:
//widget.filp() 的虚拟引发操作(virtual invocation)的转变
// (* widget.vptr[ 1 ] )( &widget )
其中:
  • 1 表示 filp() 在 virtual table 中的固定索引
  • &widget 代表要交给 “被调用的某个 filp() 函数实体” 的 this 指针
为了让这个机制发挥功效,编译器必须为每一个 Widget(或其派生类之)object 的 vptr 设定初始值,放置适当的 virtual table 地址,对于 class 所定义的每一个 constructor,编译器会插入类似前文所说的伪码来做这样的事情。对于那些什么都没有声明的 classes,编译器则就像本文主题,为它们合成一个 default constructor,以便正确的初始化每一个 class object 的 vptr。(你可以理解,vptr 必须赋初值,就像我们平时用的指针,不用是赋NULL一样)。


情况四:带有一个 ”virtual base class“ 的 class

virtual base class 的实现法在不同的编译器之间有极大差异。然后每一种实现法的共通点在于必须是 virtual base class 在其每一个 derived class object 中的位置,能够与执行期准备妥当。例如下面的代码中:
class X { public: int i; };
class A : public virtual X { public: int j; };
class B : public virtual X { public: double d; };
class C : public A, pulic B { public: int k; };

//无法在编译使其决定(resolve)出pa->X::i 的位置
void foo( const A* pa) { pa->i = 1024; }

main()
{
    foo( new A );
    foo( new C );
    // ... 
}
编译器无法固定住 foo() 函数之中"经由 pa 而存取的 X::i“ 的实际偏移位置,因为 pa 的真正类型可以改边,编译器必须改变 ”执行存取操作“ 的那些码,使 X::i 可以延迟至执行期才决定下来。
做法通常是在 “derived class object 的每一个 virtual base classes 中安插一个指针”(相当于一个中间件)。所有“经由 reference 或 pointer 来存取一个 virtual base class” 的操作可以通过相关指针完成。在我的例子中,foo() 可以被改写如下,以符合这样的实现策略:
//可能的编译器转变操做,中间加了一层
void foo( const A* pa ) { pa->__vbcX->i = 1024; }   
其中,__vbcX表示编译器产生的指针,指向 virtual base class X。__vbcX是在 class 构造期间完成的。对于 class 所定义的每一个 constructor,编译器会安插哪些 ”允许每一个 virtual base class 的执行期存取操作“ 的码。如果 class 没有声明任何 constructors,编译器必须为它合成一个 default constructor。(最后这点我的理解是,像这种多态指针不能固定下来,我们需要给它指一个方向,不能让它乱指,所以编译器会合成default constructor)。


好了,前面解释了唯有的四种编译器会为未声明 constructor 的 classes 合成一个 default constructor 的情况。现在解释一下这句:
  • 编译器合成出来的 default constructor 会明确设定 "class 内每一个 data member 的默认值                  

在合成的 default constructor 中,只有 base class subobject 和 member objects 会被初始化。所有其他的 nonstatic data member,如整数、整数指针、整数数组等等都不会被初始化,这些初始化操作对程序而言或许又需要,但对编译器则并非必要。如果一个程序需要 "把某指针设为 0” 的 default constructor,那么提供它的人应该是程序员。

                   




Silly Tavern酒馆ai角色卡与世界书导入超详细教程! 前几天,我在设置 Silly Tavern 的时候,想导入一个喜欢的角色卡,结果发现不少朋友其实已经下载好了角色资源,但却不知道怎么正确导入使用。这篇文章就作为一份完整的新手向教程,一步步教你如何在 Silly Tavern 中导入角色卡、世界书,并实现与 AI 角色的真实互动。如果你是第一次接触这个功能,建议先收藏慢慢看~点击菜单栏上的 图标,打开角色界面。初始状态下,你应该只看到默认角色 Seraphina。你可以先点击她的头像尝试聊天,也可以导入自己喜欢的角色。点击导入按钮,选择你下载好的角色卡(通 阅读详情

相关推荐

A Mathematical Framework for Transformer Circuits—(二)

transformer数学理论框架

qq_42208244的博客 758

编译器合成默认构造函数情况分析

目录 情形说明: 情况一:A类中调用了B类构造函数,若A类无构造函数,则默认合成 情形说明: 传统观念之下,如果我们没有自己定义构造函数,那么系统就会默认分配一个隐式的构造函数,但是这是不正确的。编译器比我们想的要更聪明一些,因为只有在此类必须要用构造函数时,才会隐式定义一个默认的构造函数。我们程这种构造函数为“合成的默认构造函数”。 那什么是必要情形的构造函数呢?以下就说明了必要情形。 情况一:A类中调用了B类构造函数,若A类无构造函数,则默认合成 class M0TX { p..

316

基于Simulink的前馈解耦SVPWM整流器仿真

本文提出了一种基于Simulink的前馈解耦SVPWM整流器仿真方案,针对传统双闭环控制存在的耦合严重、电压利用率低等问题进行优化。通过前馈补偿dq轴耦合项(ωLiq和ωLid)结合SVPWM调制算法,实现了电流跟踪误差<3%、电压利用率达0.99、THD<3%的性能指标。文章详细阐述了系统架构设计,包括前馈解耦原理推导、SVPWM算法实现步骤,并提供了完整的Simulink建模方法。仿真结果表明,该方案在动态响应速度(25ms)和直流电压纹波(1.1%)方面显著优于传统SPWM控制。同时给出了

MHD0815的博客 162

2.1默认构造函数隐式合成

我们都知道当user没有为一个不同的类(不符合后面讨论的几种case)定义任何构造函数时,编译器会隐式的合成一个默认构造函数,但是这个函数是空的,什么操作也不做。这就是implicit trivial default constructors。 但是编译器有时候也会合成另外一种默认构造函数implicit nontrivial default constructors,这种函数不是空的,他们会做

我从废墟中走来 721

无人机信号干扰系统设计方案

设计定向阻塞干扰器需要结合射频(RF)技术、功率放大技术和定向天线技术,核心目标是向特定方向发射高功率噪声信号,覆盖目标频段,阻断无人机的通信或导航信号。实际设计中需平衡功率、效率、散热和便携性,同时需考虑抗干扰技术的快速演进(如无人机加密通信和AI避障)。在合法框架下,此类设备可有效应对“黑飞”威胁,但技术滥用风险需严格管控。定向阻塞干扰器的硬件设计核心在于。

wanghao312的博客 3060

linux 内核vxlan收发包流程

1.vxlan口收包处理流程 前面几章详细介绍了vxlan接口的使用,而且已经知道vxlan是 MAC IN UDP中的封装,因此,在解封装之前,一切按照原有流程走,在此复习一下内核收发包流程(驱动层的数据处理这次不再解析,直接从__netif_receive_skb_core开始) #物理网卡处理中断,触发softirq i40e_intr └─napi_schedule_irqoff #i40e softirq把包放到per cpu backlog,触发softirq i40e_napi_p

魏言华的博客 3826

C++ 合成默认构造函数的真相

     对于C++默认构造函数,我曾经有两点误解: 类如果没有定义任何的构造函数,那么编译器(一定会!)将为类定义一个合成的默认构造函数。 合成默认构造函数会初始化类中所有的数据成员。   第一个误解来自于我学习C++的第一本书 《C++ Primer》,在书中392页:“只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数”。 实际上这句话也没有说错,它说明了...

weixin_34204722的博客 769

Proteus实现电子秤仿真与称重检测显示

Proteus实现电子秤仿真与称重检测显示测量原理(或设计方案)原理图代码 测量原理(或设计方案) 数字电子秤总体框图如图所示: 金属箔式应变片工作原理 电阻丝在外力作用下发生机械变形时,其电阻值发生变化,这就是电阻应变效应,描述 电阻应变效应的关系式 金属箔式应变片就是通过光刻、腐蚀等工艺制成的应变敏感元件。如图所示,将四个金属箔应变片(R1、R2、R3、R4)分别贴在双杆式悬臂梁弹性体的上下两侧,弹性体受到压力发生形变,应变片随悬臂梁形变被拉伸或被压缩 测量电路 通过应变片转换悬臂梁被测部位受力

Matataya的博客 1万+

C++编译器合成的默认函数(第二篇)

概述 前面我们整理了编译器合成的默认构造函数,也进行了深度的解析,那其他的合成函数是怎样的呢?下面就对其他的合成函数进行逐一整理。 1、析构函数 当对象被销毁的时候,会自动调用的函数。主要用来销毁类中申请的资源。格式跟构造函数差不多,只是在前面增加一个“~”符号。 注意: 对于未定义析构函数的类,编译器会默认生成一个非虚析构函数,该析构函数会调用成员以及基类的析构函数。 如果在类中申请了动态资源...

weixin_39640298的博客 2285

AUTOSAR进阶图解==>AUTOSAR_EXP_VFB

AUTOSAR虚拟功能总线(VFB)摘要 AUTOSAR虚拟功能总线(VFB)是汽车开放系统架构中的核心抽象概念,为分布式电子系统提供统一通信模型。VFB允许软件组件在不了解底层物理通信机制的情况下交互,通过运行时环境(RTE)实现组件间的数据交换。系统配置过程将抽象的VFB视图转换为具体实现,包括组件到ECU的映射和通信机制选择。VFB采用"last-is-best"语义进行数据通信,并支持模式管理机制,使组件能根据系统状态调整行为。此外,VFB还支持与非AUTOSAR系统的交互,通常

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 1117

目标检测YOLO实战应用案例100讲-基于激光雷达点云的三维目标检测算法研究

近几年来,在计算机视觉领域,利用深度学习卷积神经网络技术来实现各种任务的算法越来越多。毫米波雷达、相机、激光雷达、超声波雷达等车载传感器生产成本的降低,汽车的自动驾驶技术得到了飞速发展,在我们的日常生活中经常能看到该技术辅助汽车驾驶员进行行车。和非自动驾驶汽车相比,自动驾驶汽车存在诸多优点,如进一步减少交通事故,保障人身安全;提供安全合理的路线,避免交通拥堵;采用清洁无污染的电能作为汽车新能源,从而降低大气污染等。现如今,自动驾驶技术已经逐渐深入到各个领域并发挥着关键的作用。

qq_36130719的博客 3731

c++ 不能依赖合成的默认构造函数——变量初始化

不能依赖合成的默认构造函数 【1】编译器只有在没有发现任何构造函数的情况下才会合成默认构造函数,一旦我们定义了其他的构造函数,那么除非再定义一个默认构造函数,否则类将没有默认构造函数。 -【2】 块中的内置类型或复合类型(数组,指针)如果默认初始化,则值会为未定义,将导致错误。除非类内的这些成员全部被赋予了类内初始值。 【 3】有时,编译器不能为某些类合成默认的构造函数。 如果类内包含一个其他类类型的成员,而且该成员的类型没有默认构造函数,则无法初始化。此时我们必须自定义构造函数,否则将没有可用的默认构造

m0_37836661的博客 675

Orange3实战教程:转换---预处理

部件提供了多种预处理方法,这些方法可以在单个预处理流程中组合使用。某些方法也可作为独立部件提供,支持更高级的技术和参数调整。连接到测试与评分:在交叉验证的每个批次中应用预处理器,随后学习器的预处理器会作用于预处理后的子集。连接到学习器:覆盖学习器的默认预处理流程,仅应用自定义流程(各学习器的默认步骤详见其文档)。直接连接到原始数据并输出预处理后的数据集,可能导致模型过拟合。:从原始数据中随机选择固定数量或百分比的特征,主要用于高级测试或教学用途。的预处理器输入,同时传入数据和学习器(如。

err2008的专栏 147

c++ main函数如何调用类成员函数_C++幕后故事(二)--合成构造函数时机

1.先考虑一个问题为什么c++中有构造函数?这个可以从语言设计的角度来看这个问题。体现一个实例化的对象生命周期的完整性,一个对象在初始化的时候,让使用者有机会做些额外的初始化操作。同样,一个对象是消亡的时候,也要使用者有机会去释放资源。举个例子:吃饭前先洗手(构造函数),吃完饭在擦嘴(析构函数),这是个好习惯。但是你不洗手,不擦嘴,也没关系,只是这不是个好习惯而已(偶尔造成细菌感染,程序异常)。2...

weixin_42505503的博客 2940

计算机控制技术及应用

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

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

C++调用构造函数的方式

今天遇见了一个挺逗的问题,这个问题已经不是第一次绊脚了。放到这里,提醒自己的同时,也相信学习C++的朋友一定都会经历同样的问题。 还是老风格,先看代码: 1 class A 2 { 3 public: 4   A(int a) 5   { 6   } 7 }; 8 class B : public A 9 { 10 11 }; 12 ...

weixin_30778805的博客 768

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

C++对象模型:构造函数语意学

C++中隐式类型转换的问题在C++中,可以为类定义一个类型转换运算符,允许对象被隐式地转换成另一个类型,如operator int()可以让一个类的对象当作整数使用。但是这种隐式类型转换有时会导致意外。如为了让cin可以用于条件判断(如if (cin) ...),定义了一个operator int(),这样cin就可以被转...

cnzzs的博客 816

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C++默认成员函数

值得一提的是,是我们显式写析构函数,对于自定义类型成员也会调用他的析构,也就是说。

m0_74824865的博客 292

C++ tips 1

C++相对C而言,可能会出现的时空额外开销:1.虚函数机制,动态邦定2.虚基类3.多重继承下的“一个derived class和其第二或者后继之base的转换”virtual table中有一个指向class所关联的type_info object对指针进行类型转换,大部分情况下并不改变一个指针所含的真正地址,他只影响“被指出的内存的大小和内容”的解释方式。 编译器合成default

majcos的专栏 722

default construction

4种情况下编译器会构造出nontrivial(有用)的构造函数 带有default construction的member class object 我们有两个class: class Foo { public: Foo(), Foo(int) … }; class Bar { public: Foo foo; char *str;}; 则编译器会在Bar中合成一个默认的构

boydfd的专栏 871

C++默认构造函数的合成

编译器只在编译期需要的时候合成默认构造函数,而不是在用户需要的时候。

hhhcbw的博客,欢迎各位来访 1126

【C++】构造函数调用规则 ( 默认构造函数 | 默认无参构造函数 | 默认拷贝构造函数 | 构造函数调用规则说明 )

一、默认构造函数 1、默认无参构造函数 2、默认拷贝构造函数 二、构造函数调用规则 1、构造函数规则说明 2、代码示例 - 只定义拷贝构造函数 3、代码示例 - 只定义有参构造函数

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 6483

深入探索c++构造函数之一:默认构造函数与默认初始化

1、构造函数的基本概念 每个类都分别定义了它的对象被初始化的方式,类通过一个或者几个特殊的成员函数来控制其对象初始化的过程,这些函数叫构造函数。 关于构造函数: (1)作用:初始化对象的数据成员,只要对象创建就会被执行。 (2)返回值:无返回值 (3)参数:参数列表可以为空,函数体也可以为空 (4)构造函数重载:一个类可以包含多个构造函数,不同的构造函数之间必须在参数数量或者参数类型上有所区别。 (5)构造函数不能被声明为const的,同是当我们创建一个const对象时,只有当构造函数完成之后,对象才能真正

weixin_43354152的博客 5287

说说C++的默认构造函数

说说C++的默认构造函数

iaibeyond的专栏 1438

bool构造函数初始化_C++ 合成默认构造函数的真相

作者:melonstreet链接:https://www.cnblogs.com/QG-whz/p/4676481.html对于C++默认构造函数,我曾经有两点误解:类如果没有定义任何的构造函数,那么编译器(一定会!)将为类定义一个合成的默认构造函数。合成默认构造函数会初始化类中所有的数据成员。第一个误解来自于我学习C++的第一本书 《C++ Primer》,在书中392页:“只有当一...

weixin_42525343的博客 740

何时编译器会自动生成默认构造函数

问题:对c++初学者来说存在一个误区,如果类没有定义任何构造函数,编译器会自动生成默认的构造函数。 注意:这种说法是错误的。 正确的说法:惟有默认构造函数”被需要“的时候编译器才会合成默认构造函数。 那什么情况下是“被需要”的时候? 以下有四种情况编译器会自动合成默认构造函数 情况1含有类对象数据成员,该类对象类型有默认构造函数 即:一个class含有类对象数据成员,该类对象类型...

weixin_41066529的博客 2373

C++对象模型-默认构造函数

title: C++对象模型–默认构造函数 author: Qian Jipeng tags: 默认构造函数 初始化列表 虚函数 虚基类 vtbl vptr 对象模型 categories: C++ date: 2019-08-30 13:45:00 这篇文章主要讲解C++中默认构造函数的机制,同时解决了困扰了我很久的问题,长篇文章,话不多说,开干! 写在前面 C++编译器为程序员自动提供下...

MokeeQian的博客 2166
上一篇: muduo库chat server的shared_ptr和TLS实现分析
下一篇: C++拷贝构造函数、无名临时对象以及NRV优化分析
FreeeLinux
博客等级 码龄11年 421粉丝 356原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值