名稱空間
變體
操作

建構函式和成員初始化列表

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

建構函式是非靜態的成員函式,它們使用特殊的宣告符語法進行宣告,用於初始化其類型別的物件。

建構函式不能是協程

(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);       // 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;
};

注意:同樣適用於預設成員初始化器

[編輯] 構造和析構期間的操作

成員函式(包括虛成員函式)可以對正在構造或析構的物件進行呼叫。同樣,正在構造或析構的物件可以是typeiddynamic_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 起)

[編輯] 初始化順序

列表中成員初始化器的順序無關緊要:實際的初始化順序如下

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}, // 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]

[編輯] 參見