命名空间
变体
操作

构造函数和成员初始化列表

来自 cppreference.com
< cpp‎ | language
 
 
C++ 语言
 
 

构造函数 是用特殊声明符语法声明的非静态 成员函数,它们用于初始化其类类型的对象。

构造函数不能是 协程

(自 C++20 起)

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

(自 C++23 起)

内容

[编辑] 语法

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

class-name ( parameter-list (可选) ) except (可选) attr (可选)
class-name - 一个 标识符表达式 可能后跟一个 属性 列表,并且(自 C++11 起) 可能被一对括号包围
parameter-list - 参数列表
except -

动态异常说明

(直到 C++11)

要么是 动态异常说明
要么是 noexcept 说明

(自 C++11 起)
(直到 C++17)

noexcept 说明

(自 C++17 起)
attr - (自 C++11 起) 一个 属性 列表

在构造函数声明的 声明说明符 中,唯一允许的说明符是 friendinlineconstexpr(自 C++11 起)consteval(自 C++20 起)explicit(特别是,不允许使用返回值类型)。注意 cv 限定符和引用限定符 也不允许:正在构造的对象的 const 和 volatile 语义只有在最派生构造函数完成之后才会生效。

class-name 的标识符表达式必须具有以下形式之一

  • 对于类,标识符表达式是紧邻的类的 注入类名
  • 对于类模板,标识符表达式是 一个命名 当前实例化 的类名(在 C++20 之前)注入类名(从 C++20 开始),它是紧邻的类模板。
  • 否则,标识符表达式是一个限定标识符,其末尾非限定标识符是其 查找 上下文的注入类名。

[编辑] 成员初始化列表

任何构造函数的 函数定义 的主体,在复合语句的左花括号之前,可以包含 成员初始化列表,其语法为冒号字符 : 后跟一个或多个用逗号分隔的 成员初始化器 列表,每个初始化器都具有以下语法

类或标识符 ( 表达式列表 (可选) ) (1)
类或标识符 花括号初始化列表 (2) (自 C++11 起)
参数包 ... (3) (自 C++11 起)
1) 使用 直接初始化 初始化由 类或标识符 命名的基类或成员,或者,如果 表达式列表 为空,则使用 值初始化
2) 使用 列表初始化 初始化由 类或标识符 命名的基类或成员(如果列表为空,则变为 值初始化,在初始化聚合类型时变为 聚合初始化
3) 使用 包扩展 初始化多个基类
类或标识符 - 任何命名非静态数据成员的标识符,或任何命名类本身(用于委托构造函数)或直接或虚拟基类的类型名。
表达式列表 - 可能为空,用逗号分隔的传递给基类或成员构造函数的参数列表
花括号初始化列表 - 用花括号括起来的初始化列表
参数包 - 可变模板 参数包 的名称
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 说明符的构造函数使其类型成为 LiteralType。可以不带任何参数调用的构造函数是 默认构造函数。以相同类型对象的另一个对象作为参数的构造函数是 拷贝构造函数移动构造函数

在构成构造函数函数体的复合语句开始执行之前,所有直接基类、虚拟基类和非静态数据成员的初始化都已完成。成员初始化列表是指定这些子对象的非默认初始化的地方。对于无法进行默认初始化的基类,以及无法通过默认初始化 或通过其 默认成员初始化器(如果有的话)(从 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 处理。

可以在成员初始化器中调用成员函数(包括虚拟成员函数),但如果此时所有直接基类都没有初始化,则行为未定义。

对于虚拟调用(如果此时直接基类已初始化),与构造函数和析构函数中的虚拟调用的规则相同:虚拟成员函数的行为就好像 *this 的动态类型是正在构造的类的静态类型一样(动态分派不会传播到继承层次结构),对 纯虚拟 成员函数的虚拟调用(但不是静态调用)是未定义的行为。

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

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;
};

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

委托构造函数

如果类本身的名称出现在成员初始化列表中的 类或标识符 中,则该列表必须只包含该成员初始化器;这种构造函数被称为 委托构造函数,由初始化器列表中唯一成员选择的构造函数被称为 目标构造函数

在这种情况下,目标构造函数将通过重载解析选择并首先执行,然后控制权将返回给委托构造函数,并执行其主体。

委托构造函数不能递归。

class Foo
{
public: 
    Foo(char x, int y) {}
    Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int)
};

继承构造函数

请参阅 使用声明

(自 C++11 起)

[编辑] 初始化顺序

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

1) 如果构造函数用于最派生类,则虚拟基类按深度优先从左到右遍历基类声明的顺序进行初始化(从左到右指的是在基类说明符列表中的出现顺序)。
2) 然后,按直接基类在该类基类说明符列表中的出现顺序从左到右初始化。
3) 然后,按非静态数据成员在类定义中的声明顺序初始化。
4) 最后,执行构造函数的主体。

(注意:如果初始化顺序由不同构造函数的成员初始化列表中的出现顺序控制,则 析构函数 将无法保证析构顺序与构造顺序相反。)

[编辑] 备注

功能测试宏 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 (其生命周期将在构造函数结束时结束)
此类初始化
是非法的
CWG 1696

引用成员可以初始化为临时对象

  • (其生命周期将在构造函数结束时结束)
  • 此类初始化
  • 是非法的
  • 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++17 标准 (ISO/IEC 14882:2017)
  • 15.1 构造函数 [class.ctor]
  • C++14 标准 (ISO/IEC 14882:2014)
  • C++17 标准 (ISO/IEC 14882:2017)
  • 15.1 构造函数 [class.ctor]

12.1 构造函数 [class.ctor]

零初始化