Namespaces
Variants

Constructors and member initializer lists

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

构造函数不能是 协程

(C++20 起)

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

(C++23 起)

目录

语法

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

类名 ( 参数列表  (可选) ) 异常说明  (可选) 属性  (可选)
class-name - 一个 标识符表达式 可能后跟一组 属性 ,并 (C++11 起) 可能被一对圆括号包围
parameter-list - 形参列表
except -

动态异常规范

(C++11 前)

动态异常规范
noexcept 规范

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

noexcept 规范

(C++17 起)
attr - (C++11 起) 一组 属性

在构造函数声明的 声明说明符 中,唯一允许的说明符是 friend inline constexpr (C++11 起) consteval (C++20 起) 以及 explicit (特别强调,不允许出现返回类型)。注意 cv-限定符和引用限定符 同样不允许使用:正在构造对象的 const 和 volatile 语义仅在最终派生类的构造函数完成后生效。

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

  • 对于类,标识符表达式是直接外围类的 注入类名
  • 对于类模板,标识符表达式是 命名 当前实例化 的类名 (C++20 前) 直接外围类模板的注入类名 (C++20 起)
  • 否则,该标识符表达式是一个限定标识符,其终端非限定标识符为其 查找 上下文的注入类名。

成员初始化列表

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

成员 初始化器 (1)
初始化器 (2)
类包 初始化器 ... (3) (自 C++11 起)
1) 直接初始化 member 命名的数据成员,使用 initializer 。 member 只能命名非静态数据成员。
2) 使用 initializer 初始化类对象。 class 仅可指定以下类:
(since C++11)
  • T 的直接基类或 虚基类 。此时对应的基类子对象将使用 initializer 进行直接初始化。
3) 使用 包展开 初始化多个基类子对象。
member - 命名数据成员的标识符
class - 类名
class-pack - 展开为零个或多个类的包
initializer - 不以 = 开头的 初始化器
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 指定虚基类的初始化器将被忽略。

出现在 initializer 中的名称会在构造函数的范围内被求值:

class X
{
    int a, b, i, j;
public:
    const int& 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;   // default member initializer
    S() : n(7) {} // will set n to 7, not 42
};
(since C++11)

引用成员无法在成员初始化列表中绑定到临时对象:

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

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

构造与析构期间的操作

在对象构造或析构期间,可以调用其成员函数(包括 虚成员函数 )。同样地,正在构造或析构的对象也可以作为 typeid dynamic_cast 的操作数。

然而,若在下列任意求值过程中执行这些操作,则行为未定义:

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

委托构造函数

若类的名称本身作为 类或标识符 出现在成员初始化列表中,则该列表必须仅包含该成员初始化项;这样的构造函数称为 委托构造函数 ,而由初始化列表唯一成员选中的构造函数称为 目标构造函数

在此情况下,目标构造函数通过重载决议被选中并首先执行,随后控制权返回委托构造函数并执行其函数体。

委托构造函数不能递归。

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

继承构造函数

参见 using 声明

(since C++11)

初始化顺序

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

1) 若构造函数属于最终派生类,则虚基类将按照基类声明在深度优先从左到右遍历中的出现顺序进行初始化(从左到右指基类说明符列表中的出现顺序)。
2) 随后,直接基类按照它们在这个类的基类说明符列表中出现的顺序从左到右进行初始化。
3) 随后,非静态数据成员按照类定义中的声明顺序进行初始化。
4) 最后,执行构造函数的函数体。

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

注释

功能测试宏 标准 功能
__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}, // 初始化基类
        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 构造函数的声明符语法仅允许
最多一个函数说明符(例如构造函数
不能被声明为 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]

参见