簡體   English   中英

列表初始化(使用花括號)有什么優點?

[英]What are the advantages of list initialization (using curly braces)?

MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

為什么?

基本上是從 Bjarne Stroustrup 的“The C++ Programming Language 4th Edition”復制和粘貼:

列表初始化不允許縮小 (§iso.8.5.4)。 那是:

  • 一個整數不能轉換為另一個不能保存其值的整數。 例如,允許從 char 到 int,但不允許從 int 到 char。
  • 浮點值不能轉換為另一種不能保存其值的浮點類型。 例如,允許雙精度浮點數,但不允許雙精度浮點數。
  • 浮點值不能轉換為整數類型。
  • 整數值不能轉換為浮點類型。

例子:

void fun(double val, int val2) {

    int x2 = val;    // if val == 7.9, x2 becomes 7 (bad)

    char c2 = val2;  // if val2 == 1025, c2 becomes 1 (bad)

    int x3 {val};    // error: possible truncation (good)

    char c3 {val2};  // error: possible narrowing (good)

    char c4 {24};    // OK: 24 can be represented exactly as a char (good)

    char c5 {264};   // error (assuming 8-bit chars): 264 cannot be 
                     // represented as a char (good)

    int x4 {2.0};    // error: no double to int value conversion (good)

}

= 優於 {} 的唯一情況是使用auto關鍵字來獲取由初始化程序確定的類型。

例子:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

結論

除非您有充分的理由不這樣做,否則首選 {} 初始化而不是替代方法。

關於使用列表初始化的優點已經有了很好的答案,但是我個人的經驗法則是盡可能不要使用花括號,而是讓它依賴於概念含義:

  • 如果我正在創建的對象在概念上包含我在構造函數中傳遞的值(例如容器、POD 結構、原子、智能指針等),那么我正在使用大括號。
  • 如果構造函數類似於普通的函數調用(它執行一些或多或少復雜的操作,這些操作由參數參數化),那么我使用的是普通的函數調用語法。
  • 對於默認初始化,我總是使用花括號。
    一方面,這樣我總是確定對象被初始化,不管它是一個“真實的”類,它有一個無論如何都會被調用的默認構造函數,還是一個內置/POD類型。 其次,在大多數情況下,它與第一條規則一致,因為默認初始化對象通常表示“空”對象。

根據我的經驗,這個規則集可以比默認使用花括號更一致地應用,但是當它們不能被使用或具有與帶括號的“正常”函數調用語法不同的含義時,必須明確記住所有異常(調用不同的重載)。

例如,它非常適合標准庫類型,例如std::vector

vector<int> a{10, 20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10, 20);   //Parentheses -> uses arguments to parametrize some functionality,                          
vector<int> c(it1, it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements

使用大括號初始化有很多原因,但您應該知道initializer_list<>構造函數優於其他構造函數,默認構造函數是例外。 這會導致構造函數和模板出現問題,其中類型T構造函數可以是初始化列表或普通的舊 ctor。

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

假設您沒有遇到此類類,則沒有理由不使用初始化器列表。

只要您不使用 -Wno-narrowing 進行構建,就像 Google 在 Chromium 中所做的那樣,它只會更安全。 如果你這樣做了,那就不太安全了。 如果沒有這個標志,唯一的不安全情況將由 C++20 修復。

注意:A)大括號更安全,因為它們不允許變窄。 B)花括號不太安全,因為它們可以繞過私有或刪除的構造函數,並隱式調用顯式標記的構造函數。

這兩個組合意味着如果里面是原始常量,它們會更安全,但如果它們是對象,則安全性會降低(盡管在 C++20 中已修復)

更新 (2022-02-11):請注意,與最初發布的主題(下)相比,有更多關於該主題的最新意見,這些意見反對 {} 初始化程序的偏好,例如 Arthur Dwyer 在他關於The Knightmare的博客文章中C++ 中的初始化

原答案:

閱讀Herb Sutter 的(更新的)GotW #1 這詳細解釋了這些之間的區別,以及更多選項,以及與區分不同選項的行為相關的幾個陷阱。

第 4 節的要點/復制:

什么時候應該使用 ( ) 與 { } 語法來初始化對象? 為什么? 這是簡單的指南:

指導原則:優先使用帶{}的初始化,如vector v = { 1, 2, 3, 4 }; 或 auto v = vector{ 1, 2, 3, 4 };,因為它更一致、更正確,並且完全避免了了解舊式陷阱。 在您希望只看到 = 符號的單參數情況下,例如 int i = 42; 和自動 x = 任何東西; 省略括號很好。

這涵蓋了絕大多數情況。 只有一個主要的例外:

… 在極少數情況下,例如向量 v(10,20); 或 auto v = vector(10,20);,使用 ( ) 初始化顯式調用構造函數,否則該構造函數會被 initializer_list 構造函數隱藏。

然而,這通常應該是“罕見的”的原因是因為默認和復制構造已經很特殊並且可以與 { } 一起工作,並且好的類設計現在大多避免用戶定義構造函數的求助於()情況,因為這個最終設計指南:

指南:當你設計一個類時,避免提供一個用 initializer_list 構造函數重載的構造函數,這樣用戶就不需要使用 () 來訪問這樣一個隱藏的構造函數。

另請參閱有關該主題的核心指南: ES.23: Prefer the {}-initializer syntax

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM