构造函数与成员初始化器列表

来自cppreference.com
< cpp‎ | language


 
 
 
 

构造函数 是以一种特殊的声明符语法进行声明的非静态成员函数,它们用来初始化该类类型的对象。

构造函数不能是协程

(C++20 起)

构造函数不能有显式对象形参

(C++23 起)

目录

[编辑]语法

构造函数用以下形式的成员函数声明符声明:

类名(形参列表 (可选))异常说明 (可选)属性 (可选)
类名 - 可后随属性列表,并(C++11 起)可以圆括号包围的标识表达式
形参列表 - 形参列表
异常说明 -

动态异常说明

(C++11 前)

动态异常说明noexcept 说明

(C++11 起)
(C++17 前)

noexcept 说明

(C++17 起)
属性 - (C++11 起)属性列表

构造函数声明的声明说明符中只允许说明符 friendinlineconstexpr(C++11 起)consteval(C++20 起)explicit(尤其是不允许返回类型)。注意 cv 及引用限定符也不受允许;const 与 volatile 语义对于构造过程中的对象没有效果,它们要到最终派生类的构造函数完成才会生效。

类名 的标识表达式必须具有以下形式之一:

  • 对于类,标识表达式是立即外围类的注入类名
  • 对于类模板,标识表达式是命名了立即外围类模板的当前实例化的类名(C++20 前)立即外围类模板的注入类名(C++20 起)
  • 否则,标识表达式是一个有限定的标识符,并且它尾部的未限定的标识符是在该标识表达式的查找语境中的注入类名。

[编辑]成员初始化器列表

任何构造函数的函数定义的函数体,可以在复合语句的开花括号之前包含成员初始化器列表,它的语法是冒号字符 : 后随一个或多个成员初始化器 的逗号分隔列表,每项均具有以下语法:

类或标识符(表达式列表 (可选)) (1)
类或标识符花括号初始化器列表 (2) (C++11 起)
形参包... (3) (C++11 起)
1)直接初始化,或当表达式列表 为空时用值初始化,初始化类或标识符 所指名的基类或成员。
2)列表初始化(列表为空时进行值初始化,而在初始化聚合体时进行聚合初始化),初始化类或标识符 所指名的基类或成员。
3)包展开初始化多个基类。
类或标识符 - 任何指名非静态数据成员的标识符,或任何指名该类自身(对于委托构造函数)、直接基类或虚基类的类型名。
表达式列表 - 传递给基类或成员的实参的逗号分隔列表,可以为空
花括号初始化器列表 - 花括号包围的初始化器列表
形参包 - 变参模板形参包的名字
struct S {int n;   S(int);// 构造函数声明   S(): n(7){}// 构造函数定义:// ": n(7)" 是初始化器列表// ": n(7) {}" 是函数体};   S::S(int x): n{x}{}// 构造函数定义:": n{x}" 是初始化器列表   int main(){ S s;// 调用 S::S() S s2(10);// 调用 S::S(int)}

[编辑]解释

构造函数没有名字且无法被直接调用。它们在发生初始化时被调用,且它们按照初始化的规则进行选择。没有 explicit 说明符的构造函数是转换构造函数。有 constexpr 说明符的构造函数会让其类型成为字面类型。可以不带任何实参调用的构造函数是默认构造函数。可以接收同类型的另一对象为实参的构造函数是复制构造函数移动构造函数

在开始执行组成构造函数体的复合语句之前,所有直接基类、虚基类和非静态数据成员的初始化均已结束。这些对象的非默认初始化只能在成员初始化器列表指定。对于不能默认初始化的基类,和不能以默认初始化或以其默认成员初始化器(如果有)(C++11 起)初始化的非静态数据成员,例如引用和 const 限定的类型的成员,必须指定成员初始化器。(注意,类模板实例化的非静态数据成员的默认成员初始化器,当成员类型或初始化器待决时可能是无效的。)(C++11 起)对没有成员初始化器或默认成员初始化器(C++11 起)匿名联合体变体成员不进行初始化。

类或标识符 指名虚基类的初始化器,在并非所构造对象的最终派生类的类的构造期间被忽略。

表达式列表 或花括号初始化器列表 中出现的名字在构造函数的作用域中求值:

class X {int a, b, i, j;public:constint& r; X(int i): r(a)// 初始化 X::r 以指代 X::a , b{i}// 初始化 X::b 为形参 i 的值 , i(i)// 初始化 X::i 为形参 i 的值 , j(this->i)// 初始化 X::j 为 X::i 的值{}};

成员初始化器所抛出的异常可以被函数 try处理。

如果非静态数据成员具有默认成员初始化器且也在成员初始化器列表中出现,那么使用该成员初始化器而忽略默认成员初始化器:

struct S {int n =42;// 默认成员初始化器 S(): n(7){}// 将设置 n 为 7,而非 42};
(C++11 起)

引用成员不能绑定到成员初始化器列表中的临时量:

struct A { A(): v(42){}// 错误constint& v;};

注:这同样适用于默认成员初始化器

[编辑]在构造和析构期间的操作

可以为正在构造或析构的对象调用成员函数(包括虚成员函数)。正在构造或析构的对象也可以是 typeiddynamic_cast 的操作数。

然而如果在进行以下求值操作的过程中进行这些操作,那么行为未定义:

(C++26 起)
  • 在完成所有基类的成员初始化器 之前对成员初始化器列表求值

委托构造函数

如果类自身的名字在初始化器列表中作为类或标识符 出现,那么该列表只能由这一个成员初始化器组成;这种构造函数被称为委托构造函数,而构造函数列表的仅有成员所选择的构造函数是目标构造函数

此时首先由重载决议选择目标构造函数并予以执行,然后控制返回到委托构造函数并执行其函数体。

委托构造函数不能递归。

class Foo {public: Foo(char x, int y){} Foo(int y): Foo('a', y){}// Foo(int) 委托到 Foo(char, int)};

继承的构造函数

using 声明

(C++11 起)

[编辑]初始化顺序

列表中的成员初始化器的顺序无关紧要,初始化的实际顺序如下:

1) 如果构造函数是最终派生类的,那么按基类声明的深度优先、从左到右的遍历中的出现顺序(从左到右指的是基说明符列表中所呈现的顺序),初始化各个虚基类。
2) 然后,以在此类的基类说明符列表中出现的从左到右顺序,初始化各个直接基类。
3) 然后,以类定义中的声明顺序,初始化各个非静态成员。
4) 最后,执行构造函数体。

(注意:如果初始化的顺序是由不同构造函数中的成员初始化器列表中的出现所控制,那么析构函数就无法确保销毁顺序是构造顺序的逆序了。)

[编辑]注解

功能特性测试宏标准功能特性
__cpp_delegating_constructors200604L(C++11)委托构造函数

[编辑]示例

#include <fstream>#include <string>#include <mutex>   struct Base {int n;};   struct Class :public Base {unsignedchar x;unsignedchar y;std::mutex m;std::lock_guard<std::mutex> lg;std::fstream f;std::string s;   Class(int x): Base{123}, // 初始化基类 x(x), // x(成员)以 x(形参)初始化 y{0}, // y 初始化为 0 f{"test.cc", std::ios::app}, // 在 m 和 lg 初始化之后发生 s(__func__), //__func__ 可用,因为初始化器列表是构造函数的一部分 lg (m), // lg 使用已经初始化的 m m{}// m 在 lg 前初始化,即使它最后出现在此处{}// 空复合语句   Class(double a): y(a +1), x(y), // x 将在 y 前初始化,它的值不确定 lg(m){}// 基类初始化器未在列表中出现,它被默认初始化(这与使用 Base() 不同,那是值初始化)   Class()try// 函数 try 块在包含初始化器列表的函数体之前开始: Class(0.0)// 委托构造函数{// ...}catch(...){// 初始化中发生的异常}};   int main(){ Class c; Class c1(1); Class c2(0.1);}

[编辑]缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 194 C++98 构造函数的声明器语法最多只能有一个函数说明符
(例如无法声明构造函数为 inlineexplicit
可以有多个函数说明符
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 Constructors [class.ctor]
  • 11.9.3 Initializing bases and members [class.base.init]
  • C++20 标准(ISO/IEC 14882:2020):
  • 11.4.4 Constructors [class.ctor]
  • 11.10.2 Initializing bases and members [class.base.init]
  • C++17 标准(ISO/IEC 14882:2017):
  • 15.1 Constructors [class.ctor]
  • 15.6.2 Initializing bases and members [class.base.init]
  • C++14 标准(ISO/IEC 14882:2014):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • C++11 标准(ISO/IEC 14882:2011):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • C++98 标准(ISO/IEC 14882:1998):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]

[编辑]参阅

close