构造函数与成员初始化列表
构造函数 是使用特殊的声明符语法声明的非静态 成员函数,它们用于初始化其类类型的对象。
构造函数不能是协程。 |
(C++20 起) |
构造函数不能具有显式对象形参。 |
(C++23 起) |
内容 |
[编辑] 语法
构造函数使用下列形式的成员函数声明符声明
类名 ( 形参列表(可选) ) except(可选) attr(可选) |
|||||||||
类名 | - | 一个标识符表达式,可能后随属性列表,并且(C++11 起)可能被一对圆括号括起来 | ||||||
形参列表 | - | 形参列表 | ||||||
except | - |
| ||||||
attr | - | (C++11 起) 属性列表 |
在构造函数声明的声明说明符中,唯一允许的说明符是 friend
、 inline
、 constexpr
(C++11 起)、 consteval
(C++20 起) 和 explicit
(特别地,不允许返回类型)。注意 cv 限定符和引用限定符 也不被允许:构造中的对象的 const 和 volatile 语义只在最派生类的构造函数完成后才生效。
类名 的标识符表达式必须有下列形式之一
- 否则,标识符表达式是限定标识符,其终结的非限定标识符是其查找语境的注入类名。
[编辑] 成员初始化列表
任何构造函数的函数定义的函数体,在复合语句的左花括号之前,可以包含成员初始化列表,其语法是冒号字符 :
,后随逗号分隔的一个或多个成员初始化器列表,每个初始化器有下列语法
类或标识符 ( 表达式列表(可选) ) |
(1) | ||||||||
类或标识符 花括号初始化列表 | (2) | (C++11 起) | |||||||
形参包 ... |
(3) | (C++11 起) | |||||||
类或标识符 | - | 任何命名非静态数据成员的标识符,或任何命名类自身(对于委托构造函数)或直接或虚基类的类型名。 |
表达式列表 | - | 可能为空,逗号分隔的参数列表,用于传递给基类或成员的构造函数 |
花括号初始化列表 | - | 花括号包围的初始化器列表 |
形参包 | - | 可变参数模板形参包的名字 |
struct S { int n; S(int); // constructor declaration S() : n(7) {} // constructor definition: // ": n(7)" is the initializer list // ": n(7) {}" is the function body }; S::S(int x) : n{x} {} // constructor definition: ": n{x}" is the initializer list int main() { S s; // calls S::S() S s2(10); // calls S::S(int) }
[编辑] 解释
构造函数没有名字,不能直接调用。它们在初始化发生时被调用,并根据初始化的规则被选择。explicit 说明符的构造函数是转换构造函数。具有 constexpr 说明符的构造函数使其类型成为字面类型。可以不带任何实参调用的构造函数是默认构造函数。接受相同类型的另一个对象作为实参的构造函数是复制构造函数和移动构造函数。
在构成构造函数函数体的复合语句开始执行之前,所有直接基类、虚基类和非静态数据成员的初始化都已完成。成员初始化列表是可以指定这些子对象的非默认初始化的位置。对于不能默认初始化的基类,以及不能通过默认初始化或通过它们的默认成员初始化器(如果有)(C++11 起)来初始化的非静态数据成员,例如引用和 const 限定类型的成员,必须指定成员初始化器。(注意,如果成员类型或初始化器是依赖型的,则类模板实例化的非静态数据成员的默认成员初始化器可能无效。)(C++11 起)不为匿名联合体或没有成员初始化器或默认成员初始化器(C++11 起)的变体成员执行初始化。
当 类或标识符 命名虚基类时,初始化器在构造任何不是正被构造对象的最终派生类的类期间被忽略。
出现在 表达式列表 或 花括号初始化列表 中的名字在构造函数的作用域中求值
class X { int a, b, i, j; public: const int& r; X(int i) : r(a) // initializes X::r to refer to X::a , b{i} // initializes X::b to the value of the parameter i , i(i) // initializes X::i to the value of the parameter i , j(this->i) // initializes X::j to the value of X::i {} };
从成员初始化器抛出的异常可以被函数 try 块处理。
如果非静态数据成员具有默认成员初始化器,并且也出现在成员初始化列表,则使用成员初始化器,并忽略默认成员初始化器 struct S { int n = 42; // default member initializer S() : n(7) {} // will set n to 7, not 42 }; |
(C++11 起) |
引用成员不能在成员初始化列表中绑定到临时量
struct A { A() : v(42) {} // Error const int& v; };
注意:这同样适用于默认成员初始化器。
[编辑] 构造与析构期间的操作
成员函数(包含虚成员函数)可以为正在构造或析构的对象调用。类似地,正在构造或析构的对象可以是 typeid
或 dynamic_cast
的操作数。
但是,如果在下列求值的任何一个期间执行这些操作,则行为是未定义的
(C++26 起) |
- 在所有基类的 成员初始化器 完成之前,成员初始化列表的求值
委托构造函数如果类自身的名字作为 类或标识符 出现在成员初始化列表中,则该列表必须仅由一个成员初始化器组成;这样的构造函数被称为委托构造函数,而由初始化列表中唯一的成员选择的构造函数是目标构造函数。 在这种情况下,目标构造函数通过重载决议选择并首先执行,然后控制返回到委托构造函数并执行其函数体。 委托构造函数不能是递归的。 class Foo { public: Foo(char x, int y) {} Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int) }; 继承构造函数参见 using 声明。 |
(C++11 起) |
[编辑] 初始化顺序
成员初始化器在列表中的顺序无关紧要:实际的初始化顺序如下
(注意:如果初始化顺序由不同构造函数的成员初始化列表中的出现顺序控制,那么析构函数将无法确保析构顺序与构造顺序相反。)
[编辑] 注解
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__cpp_delegating_constructors |
200604L |
(C++11) | 委托构造函数 |
[编辑] 示例
#include <fstream> #include <string> #include <mutex> struct Base { int n; }; struct Class : public Base { unsigned char x; unsigned char y; std::mutex m; std::lock_guard<std::mutex> lg; std::fstream f; std::string s; Class(int x) : Base{123}, // initialize base class x(x), // x (member) is initialized with x (parameter) y{0}, // y initialized to 0 f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized s(__func__), // __func__ is available because init-list is a part of constructor lg(m), // lg uses m, which is already initialized m{} // m is initialized before lg even though it appears last here {} // empty compound statement Class(double a) : y(a + 1), x(y), // x will be initialized before y, its value here is indeterminate lg(m) {} // base class initializer does not appear in the list, it is // default-initialized (not the same as if Base() were used, which is value-init) Class() try // function try block begins before the function body, which includes init list : Class(0.0) // delegate constructor { // ... } catch (...) { // exception occurred on initialization } }; int main() { Class c; Class c1(1); Class c2(0.1); }
[编辑] 缺陷报告
以下行为更改的缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 194 | C++98 | 构造函数的声明符语法仅允许 至多一个函数说明符(例如,构造函数 不能声明为 inline explicit) |
允许多个函数 说明符 |
CWG 257 | C++98 | 抽象类是否应为其虚基类提供成员初始化器 是不明确的 |
指定为非必需 并且在执行期间忽略 此类成员初始化器 |
CWG 263 | C++98 | 构造函数的声明符语法 禁止构造函数为友元 |
允许构造函数 为友元 |
CWG 1345 | C++98 | 没有默认成员初始化器的匿名联合体成员 被默认初始化 |
它们未被初始化 |
CWG 1435 | C++98 | “类名”在 构造函数的声明符语法中的含义不明确 |
将语法更改为专门的 函数声明符语法 |
CWG 1696 | C++98 | 引用成员可以初始化为临时量 (其生命周期将在构造函数结束时结束) |
此类初始化 是非良构的 |
[编辑] 引用
- C++23 标准 (ISO/IEC 14882:2024)
- 11.4.5 构造函数 [class.ctor]
- 11.9.3 初始化基类和成员 [class.base.init]
- C++20 标准 (ISO/IEC 14882:2020)
- 11.4.4 构造函数 [class.ctor]
- 11.10.2 初始化基类和成员 [class.base.init]
- C++17 标准 (ISO/IEC 14882:2017)
- 15.1 构造函数 [class.ctor]
- 15.6.2 初始化基类和成员 [class.base.init]
- C++14 标准 (ISO/IEC 14882:2014)
- 12.1 构造函数 [class.ctor]
- 12.6.2 初始化基类和成员 [class.base.init]
- C++11 标准 (ISO/IEC 14882:2011)
- 12.1 构造函数 [class.ctor]
- 12.6.2 初始化基类和成员 [class.base.init]
- C++98 标准 (ISO/IEC 14882:1998)
- 12.1 构造函数 [class.ctor]
- 12.6.2 初始化基类和成员 [class.base.init]