直接初始化

出自cppreference.com


 
 
C++ 語言
 
 

以一組明確的構造函數實參對對象進行初始化。

語法

T 對象 ( 實參 );

T 對象 ( 實參1, 實參2, ... );

(1)
T 對象 { 實參 }; (2) (C++11 起)
T ( 其他對象 )

T ( 實參1, 實參2, ... )

(3)
static_cast< T >( 其他對象 ) (4)
new T(實參列表, ...) (5)
::() : 成員(實參列表, ...) { ... } (6)
[實參](){ ... } (7) (C++11 起)

解釋

在下列場合進行直接初始化:

1) 以表達式或花括號初始化列表(C++11 起)的非空帶括號列表初始化。
2) 以花括號環繞的單個初始化器初始化一個非類類型對象(注意:對於類類型和其他使用花括號初始化列表的初始化,見列表初始化)。
3)函數風格轉換或以帶括號的表達式列表初始化純右值臨時量(C++17 前)純右值的結果對象(C++17 起)
4)static_cast 表達式初始化純右值臨時量(C++17 前)純右值的結果對象(C++17 起)
5) 用帶有非空初始化器的 new 表達式初始化具有動態存儲期的對象。
6) 用構造函數初始化器列表初始化基類或非靜態成員。
7) 在 lambda 表達式中從按複製捕獲的變量初始化閉包對象的成員。

直接初始化的效果是:

  • 如果 T 是數組類型,那麼
  • 程序非良構。
(C++20 前)
struct A
{
    explicit A(int i = 0) {}
};

A a[2](A(1)); // OK:以 A(1) 初始化 a[0] 并以 A() 初始化 a[1]
A b[2]{A(1)}; // 错误:从 {} 隐式复制初始化 b[1] 选择了 explicit 构造函数
(C++20 起)
  • 如果 T 是類類型,
  • 如果初始化器是純右值表達式且類型與 T 為相同的類(忽略 cv 限定),則用初始化器表達式自身,而非從它實質化的臨時量,初始化目標對象:
    (C++17 前,編譯器可以在此情況下消除源自純右值的構造,但適合的構造函數仍必須可訪問:參閱複製消除
(C++17 起)
  • 檢驗 T 的構造函數並由重載決議選取最佳匹配。然後調用該構造函數以初始化對象。
  • 否則,如果目標類型是(可能有 cv 限定)的聚合類��則按聚合初始化中所述進行初始化,但允許窄化轉換,不允許使用指派初始化器,不延長引用所綁定到的臨時量的生存期,不進行花括號消除,並值初始化任何無初始化器的元素。
struct B
{
    int a;
    int&& r;
};

int f();
int n = 10;

B b1{1, f()};            // OK:延长生存期
B b2(1, f());            // 良构,但有悬垂引用
B b3{1.0, 1};            // 错误:窄化转换
B b4(1.0, 1);            // 良构,但有悬垂引用
B b5(1.0, std::move(n)); // OK
(C++20 起)
  • 否則,如果 T 是非類類型但源類型是類類型,則檢驗源類型及其各基類的轉換函數,並由重載決議選取最佳匹配。然後用選取的用戶定義轉換,將初始化器表達式轉換為所初始化的對象。
  • 否則,如果 Tbool 而原類型是 std::nullptr_t,則被初始化對象的值為 false
  • 否則,在必要時使用標準轉換,轉換 其他對象 的值為 T 的無 cv 限定版本,而所初始化的對象的初值為(可能為轉換後的)該值。

註解

直接初始比複製初始化更寬鬆:複製初始化僅考慮非顯式的構造函數和非顯式的用戶定義轉換函數,而直接初始化考慮所有構造函數和所有用戶定義轉換函數。

在使用直接初始化語法 (1)(帶圓括號)的變量聲明和函數聲明之間有歧義的情況下,編譯器始終選擇函數聲明。此消歧義規則有時是反直覺的,並且已被稱為最煩人的分析

#include <fstream>
#include <iterator>
#include <string>

int main()
{
    std::ifstream file("data.txt");

    // 下面是函数声明:
    std::string foo1(std::istreambuf_iterator<char>(file),
                     std::istreambuf_iterator<char>());
    // 它声明名为 str 的函数,其返回类型为 std::string,
    // 第一参数拥有 std::istreambuf_iterator<char> 类型和名称 "file"
    // 第二参数无名称并拥有类型 std::istreambuf_iterator<char>(),
    // 它被重写成函数指针类型 std::istreambuf_iterator<char>(*)()
    
    // C++11 前的修正(用以声明变量):用额外括号环绕其中一个实参
    std::string str1((std::istreambuf_iterator<char>(file)),
                      std::istreambuf_iterator<char>());
    
    // C++11 后的修正(用以声明变量):对任意实参使用列表初始化
    std::string str2(std::istreambuf_iterator<char>{file}, {});
}

示例

#include <iostream>
#include <memory>
#include <string>

struct Foo
{
    int mem;
    explicit Foo(int n) : mem(n) {}
};

int main()
{
    std::string s1("test"); // 自 const char* 的构造函数
    std::string s2(10, 'a');

    std::unique_ptr<int> p(new int(1));  // OK:允许 explicit 构造函数
//  std::unique_ptr<int> p = new int(1); // 错误:构造函数为 explicit

    Foo f(2); // f 被直接初始化:
              // 构造函数形参 n 从右值 2 复制初始化
              // f.mem 从形参 n 直接初始化
//  Foo f2 = 2; // 错误:构造函数为 explicit

    std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem  << '\n';
}

輸出:

test aaaaaaaaaa 1 2

參閱