构造函数和成员初始化列表
构造函数是非静态的成员函数,它们使用特殊的声明符语法进行声明,用于初始化其类类型的对象。
构造函数不能是协程。 |
(C++20 起) |
构造函数不能有显式对象参数。 |
(C++23 起) |
目录 |
[编辑] 语法
构造函数使用以下形式的成员函数声明符进行声明
类名 ( 参数列表 (可选) ) 异常规范 (可选) 属性 (可选) |
|||||||||
类名 | - | 一个标识符表达式,可能后跟属性列表,并(C++11 起)可能用一对括号括起来 | ||||||
参数列表 | - | 参数列表 | ||||||
异常规范 | - |
| ||||||
属性 | - | (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 起) |
[编辑] 初始化顺序
列表中成员初始化器的顺序无关紧要:实际的初始化顺序如下
(注意:如果初始化顺序由不同构造函数的成员初始化列表中出现的顺序控制,则析构函数将无法确保析构顺序与构造顺序相反。)
[编辑] 注意
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
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]