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

来自cppreference.com
< cpp‎ | language

构造函数是类的特殊非静态成员函数,用于初始化其类类型的对象。

在类的构造函数定义中,成员初始化器列表指定直接和虚基类子对象和非静态数据成员的初始化器。 (勿与 std::initializer_list 混淆)

目录

[编辑] 语法

构造函数用下列形式的成员函数声明器声明:

class-name ( parameter-list(可选) ) except-spec(可选) attr(可选) (1)

其中 class-name 必须指名当前类(或当前类模板的实例),或当声明于命名空间作用域或在友元声明时,它必须是限定的类名。

构造函数声明的 decl-specifier-seq 中仅允许指定符 friendinlineexplicitconstexpr (特别是不允许返回类型)。注意 cv 及引用限定符也不受允许;对象的 const 与 volatile 语义在构造中无效果,直至最终导出类的构造函数完成。

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

class-or-identifier ( expression-list(可选) ) (1)
class-or-identifier brace-init-list (2) (C++11 起)
parameter-pack ... (3) (C++11 起)
1)直接初始化,或若 expression-list 为空,则用值初始化初始化 class-or-identifier 所指名的基类或成员
2)列表初始化(若列表为空则成为值初始化且在初始化聚合体时为聚合初始化)初始化 class-or-identifier 所指名的基类或成员
3)包展开初始化多个基类
class-or-identifier - 任何标识符、类名或 decltype 表达式,它指名一个非静态数据成员、直接或虚基类,或(对于委托构造函数)类自身
expression-list - 可为空、传递给基类或成员的参数的逗号分隔列表
braced-init-list - 初始化器和嵌套花括号初始化器的花括号环绕列表
parameter-pack - 变长模板参数包名称
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 指定符的函数令其类型为字面类型 (LiteralType) 。可以不带任何参数调用的构造函数是默认构造函数。可以接收同类型的另一对象为参数的构造函数是复制构造函数移动构造函数

在组成构造函数体的复合语句执行前,所有直接基类、虚基类,及非静态数据成员的初始化结束。成员初始化器列表是能指定这些对象非默认初始化之处。对于不能默认初始化的成员,例如引用成员和 const 限定类型,必须指定成员初始化器。对无成员初始化器的匿名联合体变体成员不进行初始化。

class-or-identifier 指名虚基类的初始化器,在任何被构造对象的非最终导出类的构造函数执行期间被忽略。

出现于 expression-listbrace-init-list 的名称于构造函数的作用域中求值:

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 块所处理。

成员函数(包含虚成员函数)可从成员初始化器调用,但若在该点不是所有直接基类都被初始化,则行为未定义。

对于虚函数调用(若基类被初初始化),应用与从构造函数与析构函数虚调用相同的规则:虚成员函数表现如同 *this 的动态类型是正在构造的类(动态派发不在继承层级下传),而对纯虚成员函数的虚调用(但非静态调用)是未定义行为。

若非静态数据成员拥有 默认成员初始化器且亦出现于成员初始化器列表,则执行成员初始化器列表,而忽略默认成员初始化器:

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

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

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

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

(C++14 起)

委托构造函数

若类名自身于初始化器列表作为 class-or-identifier 出现,则列表自身必须仅有那一个成员初始化器组成;这种构造函数被称为委托构造函数,而构造函数列表的仅有成员所选择的构造函数是目标构造函数

此情况下,目标构造函数为重载决议所选择,并首先执行,然后控制转移到委托构造函书并执行其体。

委托构造函数不能递归。

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) 最后,执行构造函数体

(注意:若初始化顺序为不同构造函数的成员初始化器列表中的出现所控制,则析构函数不能确保析构顺序为构造顺序的逆序)

[编辑] 示例

#include <fstream>
#include <mutex>
 
struct Base {
    int n;
    Base(int n) : n(n) {}
};   
 
struct Class : public Base
{
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f1, f2;
 
    Class ( int x )
      : Base ( 123 ), // 初始化基类
        x ( x ),      // x (成员)以 x (参数)初始化
        y { 0 },      // y 初始化为 0
        f1{"test.cc", std::ios::app}, // 这发生在 m 与 lg 初始化后
        f2("test.bin", std::ios::binary|std::ios::app),
        lg ( m ),      // lg 使用 m ,它已被初始化
        m{}            // m 初始化先于 lg ,即使它于复合语句最后出现
    {}
 
    Class ( double a )
      : Base ( 0 ),
        y ( a+1 ),
        x ( y ), // x 将在 y 前初始化,此处其值不确定
        lg ( m )
    {} // 基类构造函数不出现于列表中,它被默认初始化(不同于使用 Base() ,其为值初始化)
 
    Class()
    try // 函数 try 块在包含初始化器列表的函数体前开始
      : Base ( 789 ),
        x ( 0 ),
        y ( 0 ),
        lg ( m )
    {
        // ...
    }
    catch (...)
    {
        // 初始化中发生异常
    }
};
 
int main() {
    Class c;
    Class c1(1);
    Class c2(0.1);
}

[编辑] 缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1696 C++14 引用成员能初始化到临时量(生存期于构造函数结尾结束者) 这种初始化为病态

[编辑] 引用

  • C++11 standard (ISO/IEC 14882:2011):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • C++98 standard (ISO/IEC 14882:1998):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]