2020-12-18(何种情况下编译器会提供默认构造函数)

博客讨论了编译器提供默认构造函数的情况。指出并非在未定义构造函数时就提供,而是在两种情况下才会提供:一是本类成员对象或父类有虚函数;二是父类或本类成员对象带有构造函数。若不满足这两种情况,提供默认构造函数会降低程序执行效率。

当问到大家什么时候会提供默认构造函数时,有的人会不会一下子就想到当我们没有定义构造函数的时候,它就会提供默认的构造函数,但我们想想一个问题,假如没有意义的情况下提供了默认构造函数,那是不是把编译器搞得像一个傻子一样?其实呀,编译器不傻。
所以总结如下:

下面两种情况下,编译器才会提供默认的构造函数:
a.本类.本类中定义的成员对象或者父类中有虚函数存在
解释:由于需要初始化虚表,且这个工作理应在构造函数隐式完成,因此在没有定义构造函数的情况下,编译器会添加默认的构造函数用于隐式完成虚表的创建工作

b.父类或本类中定义的成员对象带有构造函数
解释:在对象被定义时,由于对象本身为派生类,因此构造顺序实现构造父类再构造自身。当父类中带有构造函数时,将会调用父类的构造函数,而这个调用过程需要在构造函数中完成,因此编译器添加了默认的构造函数来完成这个调用过程。成员对象带有构造函数的情况与此相同

在没有定义构造函数的情况下,当类中没有虚函数存在时,父类和成员对象也没有定义构造函数时,提供默认构造函数已没有任何意义,只会降低程序的执行效率。

<think> 我们正在讨论C++中编译器自动合成构造函数情况以及表述的准确性。 根据C++标准,在某些情况下,如果程序员没有为一个类显式定义构造函数编译器会隐式声明一个默认构造函数。这个过程通常被称为“自动合成”或“隐式生成”。 引用[1]提到函数原型(声明)和函数定义的区别,这里不直接相关,但提醒我们注意声明和定义的区别:编译器隐式声明构造函数,并在需要时定义(生成)它。 引用[2]中关于函数原型的讨论,可以帮助我们理解编译器为什么需要知道函数的类型信息。同样,对于构造函数编译器需要知道如何构造对象。 引用[3]是关于类型推导的,可以间接关联,因为编译器在生成构造函数时涉及类型处理。 引用[4]强调按照ISO标准C++,因此我们将依据C++标准来回答。 重点:根据C++标准,编译器在以下情况下会合成默认构造函数(注意:是“合成”还是“隐式声明并定义”?通常,我们使用“隐式生成”或“合成”都是可以接受的,但要注意其含义)。 经典著作《Effective C++》和《Inside the C++ Object Model》对此有详细描述。 情况: 1. 当类成员中含有带有默认构造函数的类对象,并且该对象所属的类没有用户定义的构造函数,那么编译器会生成一个默认构造函数,以调用该成员的默认构造函数。 2. 基类带有默认构造函数的派生类:如果一个派生类的基类有默认构造函数(可以是用户定义或编译器合成),而派生类没有定义任何构造函数,那么编译器会为该派生类合成一个默认构造函数,以调用基类的默认构造函数。 3. 类中声明了虚函数(需要初始化虚函数表指针)。 4. 虚继承(需要初始化虚基类指针或偏移量)。 但是,注意:C++11以后,规则有所变化,例如如果类中有成员变量在声明时进行了初始化(类内初始化),那么编译器也会为它生成默认构造函数。 另外,如果用户定义了任何构造函数(包括拷贝构造函数、移动构造函数等),编译器就不会再生成默认的无参构造函数,除非显式要求(使用=default)。 关于表述的准确性: “自动合成构造函数”是一种常见的表述,在Scott Meyers的书籍和许多文献中都有使用。但是,严格来说,按照C++标准,编译器隐式声明并定义默认构造函数。术语“合成”更偏向于实现层面,而标准中描述为“隐式声明”和“隐式定义”。 因此,我们可以说: - 在用户没有显式声明构造函数情况下编译器会隐式声明一个默认构造函数- 如果这个隐式声明的默认构造函数被调用(即需要创建对象),编译器会隐式定义它(即生成函数体)。这个定义的过程就是“合成”。 所以,表述“编译器自动合成构造函数”是准确的,但要注意语境。在学术讨论中,我们通常使用标准术语“隐式声明”和“隐式定义”。 总结: 编译器在以下情况下会隐式生成(合成)默认构造函数: 1. 类中含有带有默认构造函数的成员对象(且该成员类没有用户定义的构造函数?注意:只要成员类有可访问的默认构造函数,无论它是用户定义还是编译器生成,都会导致合成)。 2. 基类有默认构造函数(同样,无论基类的默认构造函数是用户定义还是编译器生成)。 3. 类含有虚函数(需要初始化虚表指针)。 4. 类虚继承其他类(需要处理虚基类)。 5. (C++11起)类中有非静态数据成员使用类内初始化器,且没有用户提供构造函数。 注意:如果用户声明了任何构造函数(包括拷贝构造函数、移动构造函数等),编译器就不会再隐式生成默认构造函数。但用户可以使用`=default`要求编译器生成。 另外,合成的默认构造函数只会执行必要的操作(如调用基类或成员的构造函数,初始化虚表指针等),而不会初始化内置类型的非静态数据成员(除非有类内初始化器)。 因此,对于内置类型成员,如果没有类内初始化,则其值将是未定义的。 示例代码: 以下情况编译器会合成默认构造函数: ```cpp class Base {}; class Member { public: Member() {} // 用户定义的默认构造函数 }; class MyClass : public Base { // 基类Base有编译器合成的默认构造函数(隐式生成的) Member m; // 成员对象m有默认构造函数 int a; // 内置类型,没有初始化(在合成的默认构造函数中不会初始化) }; // 使用 MyClass obj; // 这里,编译器会为MyClass合成一个默认构造函数,用于调用Base的默认构造函数和Member的默认构造函数,但a是未初始化的。 ``` 引用标准:C++标准文档(如ISO/IEC 14882)的相关章节(如C++11标准的12.1节)。 注意:在C++11之后,如果类中有成员变量使用了类内初始化,那么即使没有用户定义的构造函数编译器也会合成默认构造函数来初始化这些成员(按照类内初始化器)。 例如: ```cpp class WithInit { int x = 10; // 类内初始化 }; // 编译器会合成默认构造函数,将x初始化为10。 ``` 因此,“自动合成构造函数”的表述是准确的,但需要理解其背后的规则。 相关问题: 1. 什么情况下编译器会自动合成拷贝构造函数? 2. 合成的默认构造函数会初始化哪些成员?不会初始化哪些成员? 3. C++11中类内初始化对合成构造函数有什么影响? 4. 如何阻止编译器自动合成构造函数? 5. 使用“=default”和编译器自动合成构造函数有什么区别? </think> ### C++编译器自动合成构造函数情况及表述准确性分析 #### 1. **自动合成构造函数情况** 根据C++标准(ISO/IEC 14882),编译器在以下场景会自动合成(隐式生成)默认构造函数: ##### (1) **类成员包含默认构造对象** 当类包含**具有默认构造函数的成员对象**,且该类没有声明任何构造函数时: ```cpp class Member { public: Member() {} // 用户定义的默认构造函数 }; class MyClass { Member m; // 触发MyClass默认构造函数的合成 int x; }; // 编译器合成 MyClass::MyClass() ``` ##### (2) **基类包含默认构造函数** 当派生类的**基类有默认构造函数**,且派生类未声明任何构造函数时: ```cpp class Base { public: Base() {} }; class Derived : public Base { // 编译器合成 Derived::Derived() 以调用 Base() }; ``` ##### (3) **虚函数或虚继承** 类声明**虚函数**或**虚继承**时: ```cpp class VirtualBase {}; class MyClass : virtual public VirtualBase { // 虚继承 virtual void foo() {} // 虚函数 }; // 合成默认构造函数初始化虚表指针 ``` ##### (4) **C++11 类内初始化** 类中有成员使用**类内初始化器**(C++11 起): ```cpp class WithInit { int x = 10; // 类内初始化 }; // 合成默认构造函数用于初始化 x ``` #### 2. **"自动合成"表述的准确性** - **表述合理但需注意细节**: - ✅ **标准术语**:C++标准称此为 *implicitly declared*(隐式声明)和 *implicitly defined*(隐式定义),"合成"是通用技术表述(见《Inside the C++ Object Model》)[^1]。 - ⚠️ **条件限制**:合成仅在**未被用户覆盖**时发生(若用户定义任意构造函数,则不会合成默认构造函数)。 - ❌ **不完整初始化**:合成的构造函数**不会初始化内置类型成员**(如 `int`),除非有类内初始化器: ```cpp class Problem { int y; // 未初始化! }; Problem p; // p.y 值随机 ``` #### 3. **验证编译器行为的方法** 可参考引用[^3]的方法获取合成构造函数的类型信息: ```cpp template<typename T> class TD; // 类型探测器 class Test {}; Test t; TD<decltype(t)> t_type; // 编译错误信息显示实际类型 ``` 编译错误将显示合成的构造函数签名(如 `Test::Test()`)。 #### 4. **标准合规性建议** 遵循引用[^4]的准则: > 优先使用 ISO 标准 C++,必要时封装平台相关扩展。编译器合成行为是标准核心机制,但需明确其边界条件: > - 内置类型成员需手动初始化 > - 虚函数处理依赖编译器实现 > - 移动构造函数的合成规则(C++11) --- ### 相关问题 1. **合成构造函数与用户自定义构造函数共存时会发生什么?** [提示:用户定义构造函数会阻止默认构造函数的合成] 2. **如何显式阻止编译器合成构造函数?** [提示:使用 `= delete` 或声明为 `private`] 3. **C++11 后合成构造函数的行为有哪些关键变化?** [提示:类内初始化、默认成员初始化] 4. **合成构造函数对 POD(Plain Old Data)类型有何影响?** [提示:POD 类型是否触发合成?] 5. **虚函数表(vtable)如何在合成的构造函数中初始化?** [提示:不同编译器的实现差异]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦&之璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值