Tại sao khởi tạo danh sách (sử dụng dấu ngoặc nhọn) tốt hơn so với các lựa chọn thay thế?


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

Tại sao?

Tôi không thể tìm thấy câu trả lời trên SO, vì vậy hãy để tôi trả lời câu hỏi của riêng tôi.


12
Tại sao không sử dụng auto?
Mark Garcia

33
Điều đó đúng, nó thuận tiện, nhưng theo tôi thì nó làm giảm khả năng đọc - tôi muốn xem loại đối tượng là gì khi đọc mã. Nếu bạn chắc chắn 100% loại đối tượng là gì, tại sao nên sử dụng auto? Và nếu bạn sử dụng khởi tạo danh sách (đọc câu trả lời của tôi), bạn có thể chắc chắn rằng nó luôn luôn đúng.
Oleksiy

102
@Oleksiy: std::map<std::string, std::vector<std::string>>::const_iteratormuốn một lời với bạn.
Xèo

9
@Oleksiy Tôi khuyên bạn nên đọc GotW này .
Rapptz

17
@doc tôi muốn nói using MyContainer = std::map<std::string, std::vector<std::string>>;là tốt hơn (đặc biệt là khi bạn có thể template nó!)
JAB

Câu trả lời:


357

Về cơ bản sao chép và dán từ "Phiên bản thứ 4 của ngôn ngữ lập trình C ++" của Bjarne Stroustrup :

Danh sách khởi tạo không cho phép thu hẹp (§iso.8.5.4). Đó là:

  • Một số nguyên không thể được chuyển đổi thành một số nguyên khác không thể giữ giá trị của nó. Ví dụ: char to int được cho phép, nhưng không cho phép int.
  • Không thể chuyển đổi giá trị của dấu phẩy động sang loại dấu phẩy động khác mà không thể giữ giá trị của nó. Ví dụ, float to double được cho phép, nhưng không được double để float.
  • Không thể chuyển đổi giá trị dấu phẩy động thành loại số nguyên.
  • Một giá trị số nguyên không thể được chuyển đổi thành loại dấu phẩy động.

Thí dụ:

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)

}

Các chỉ tình huống mà = được ưa thích hơn {} là khi sử dụng autotừ khóa để có được những loại xác định bởi initializer.

Thí dụ:

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

Phần kết luận

Thích khởi tạo {} hơn các lựa chọn thay thế trừ khi bạn có lý do chính đáng để không.


52
Ngoài ra còn có một thực tế là việc sử dụng ()có thể được phân tích cú pháp như một khai báo hàm. Thật khó hiểu và không nhất quán mà bạn có thể nói T t(x,y,z);nhưng không T t(). Và đôi khi, bạn chắc chắn x, bạn thậm chí không thể nói T t(x);.
juanchopanza

84
Tôi hoàn toàn không đồng ý với câu trả lời này; khởi tạo giằng co trở thành một mớ hỗn độn khi bạn có các kiểu với một ctor chấp nhận a std::initializer_list. RedXIII đề cập đến vấn đề này (và chỉ cần loại bỏ nó), trong khi bạn hoàn toàn bỏ qua nó. A(5,4)A{5,4}có thể gọi các chức năng hoàn toàn khác nhau, và đây là một điều quan trọng cần biết. Nó thậm chí có thể dẫn đến các cuộc gọi có vẻ không trực quan. Nói rằng bạn nên {}mặc định sẽ dẫn đến việc mọi người hiểu nhầm những gì đang diễn ra. Đây không phải là lỗi của bạn. Cá nhân tôi nghĩ rằng đó là một tính năng cực kỳ nghèo nàn.
dùng1520427

13
@ user1520427 Đó là lý do tại sao có phần " trừ khi bạn có lý do mạnh mẽ để không ".
Oleksiy

67
Mặc dù câu hỏi này đã cũ nhưng nó có khá nhiều câu hỏi vì vậy tôi thêm nó ở đây chỉ để tham khảo (tôi chưa thấy nó ở bất kỳ nơi nào khác trên trang). Từ C ++ 14 với Quy tắc mới để tự động khấu trừ từ danh sách khởi động , giờ đây có thể viết auto var{ 5 }và nó sẽ được suy luận là intkhông còn nữa std::initializer_list<int>.
Edoardo Sparkon Dominici

13
Haha, từ tất cả các ý kiến ​​vẫn chưa rõ phải làm gì. Điều rõ ràng là, đặc tả C ++ là một mớ hỗn độn!
Trống

113

Đã có câu trả lời tuyệt vời về những lợi ích của việc sử dụng khởi tạo danh sách, tuy nhiên quy tắc cá nhân của tôi là KHÔNG sử dụng dấu ngoặc nhọn bất cứ khi nào có thể, mà thay vào đó làm cho nó phụ thuộc vào ý nghĩa khái niệm:

  • Nếu đối tượng tôi đang tạo về mặt khái niệm giữ các giá trị mà tôi truyền trong hàm tạo (ví dụ: container, cấu trúc POD, nguyên tử, con trỏ thông minh, v.v.), thì tôi đang sử dụng dấu ngoặc nhọn.
  • Nếu hàm tạo giống như một lệnh gọi hàm bình thường (nó thực hiện một số thao tác phức tạp hơn hoặc ít hơn được tham số hóa bởi các đối số) thì tôi đang sử dụng cú pháp gọi hàm bình thường.
  • Để khởi tạo mặc định tôi luôn sử dụng dấu ngoặc nhọn.
    Đối với một người, theo cách đó, tôi luôn chắc chắn rằng đối tượng được khởi tạo bất kể nó là lớp "thực" với hàm tạo mặc định sẽ được gọi bằng cách nào hoặc kiểu dựng sẵn / POD. Thứ hai, đó là - trong hầu hết các trường hợp - phù hợp với quy tắc đầu tiên, vì một đối tượng khởi tạo mặc định thường đại diện cho một đối tượng "trống".

Theo kinh nghiệm của tôi, quy tắc này có thể được áp dụng nhất quán hơn nhiều so với sử dụng dấu ngoặc nhọn theo mặc định, nhưng phải nhớ rõ tất cả các ngoại lệ khi chúng không thể được sử dụng hoặc có ý nghĩa khác với cú pháp gọi hàm "thông thường" với dấu ngoặc đơn (gọi là quá tải khác nhau).

Nó ví dụ phù hợp độc đáo với các loại thư viện tiêu chuẩn như 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

11
Hoàn toàn đồng ý với hầu hết các câu trả lời của bạn. Tuy nhiên, bạn không nghĩ rằng đặt niềng răng trống cho vector chỉ là dư thừa? Ý tôi là, không sao, khi bạn cần khởi tạo giá trị một đối tượng thuộc loại T chung, nhưng mục đích của việc làm nó đối với mã không chung chung là gì?
Mikhail

8
@Mikhail: Nó chắc chắn là dư thừa, nhưng đó là thói quen của tôi khi luôn luôn làm cho việc khởi tạo biến cục bộ trở nên rõ ràng. Như tôi đã viết, điều này chủ yếu là về sự đồng thuận vì vậy tôi không quên điều đó, khi nó quan trọng. Nó chắc chắn không có gì tôi đề cập trong một đánh giá mã hoặc đưa vào một hướng dẫn phong cách mặc dù.
MikeMB

4
quy tắc khá sạch sẽ.
laike9m

5
Đây là câu trả lời tốt nhất. {} giống như thừa kế - dễ lạm dụng, dẫn đến mã khó hiểu.
UKMonkey

2
Ví dụ @MikeMB: const int &b{}<- không cố tạo tham chiếu chưa được khởi tạo, nhưng liên kết nó với một đối tượng số nguyên tạm thời. Ví dụ thứ hai: struct A { const int &b; A():b{} {} };<- không cố gắng tạo một tham chiếu chưa được khởi tạo (như ()sẽ làm), nhưng liên kết nó với một đối tượng số nguyên tạm thời, và sau đó để nó lơ lửng. GCC thậm chí -Wallkhông cảnh báo cho ví dụ thứ hai.
Julian Schaub - litb

91

Có NHIỀU lý do để sử dụng khởi tạo dấu ngoặc, nhưng bạn nên lưu ý rằng hàm initializer_list<>tạo được ưu tiên hơn các hàm tạo khác , ngoại lệ là hàm tạo mặc định. Điều này dẫn đến các vấn đề với các hàm tạo và các mẫu trong đó hàm tạo kiểu Tcó thể là danh sách khởi tạo hoặc một ctor cũ đơn giản.

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!!!
}

Giả sử bạn không gặp phải các lớp như vậy, có rất ít lý do để không sử dụng danh sách intializer.


20
Đây là một điểm rất quan trọng trong lập trình chung. Khi bạn viết mẫu, không sử dụng braces-init-list (tên của tiêu chuẩn { ... }) trừ khi bạn muốn initializer_listngữ nghĩa (tốt, và có thể để xây dựng một đối tượng mặc định).
Xèo

82
Tôi thực sự không hiểu tại sao std::initializer_listquy tắc thậm chí tồn tại - nó chỉ thêm sự nhầm lẫn và lộn xộn cho ngôn ngữ. Có gì sai khi làm Foo{{a}}nếu bạn muốn nhà std::initializer_listxây dựng? Điều đó có vẻ dễ hiểu hơn nhiều so với việc std::initializer_listđược ưu tiên hơn tất cả các tình trạng quá tải khác.
dùng1520427

5
+1 cho nhận xét trên, vì tôi nghĩ nó thực sự lộn xộn !! nó không logic; Foo{{a}}theo một số logic cho tôi nhiều hơn so với Foo{a}việc chuyển thành ưu tiên danh sách intializer (người dùng có thể nghĩ rằng hm ...)
Gabriel

32
Về cơ bản C ++ 11 thay thế một mớ hỗn độn này bằng một mớ hỗn độn khác. Ồ, xin lỗi, nó không thay thế nó - nó thêm vào nó. Làm thế nào bạn có thể biết nếu bạn không gặp phải các lớp như vậy? Điều gì nếu bạn bắt đầu mà không cần std::initializer_list<Foo> xây dựng, nhưng nó sẽ được bổ sung vào Foolớp tại một số điểm để mở rộng giao diện của nó? Sau đó, người dùng của Foolớp được vít lên.
doc

10
.. "NHIỀU lý do để sử dụng khởi tạo cú đúp" là gì? Câu trả lời này chỉ ra một lý do ( initializer_list<>) mà nó không thực sự đủ điều kiện ai nói nó được ưa thích, và sau đó tiến hành đề cập đến một trường hợp tốt trong đó KHÔNG được ưa thích. Tôi còn thiếu điều gì khi ~ 30 người khác (tính đến 2016-04-21) thấy hữu ích?
lùn

0

Nó chỉ an toàn hơn miễn là bạn không xây dựng với - Thu hẹp như Google nói trong Chromium. Nếu bạn làm, thì đó là ÍT an toàn. Không có cờ đó, các trường hợp không an toàn sẽ được sửa bởi C ++ 20.

Lưu ý: A) Dấu ngoặc nhọn an toàn hơn vì chúng không cho phép thu hẹp. B) Người lùn xoăn ít an toàn hơn vì họ có thể bỏ qua các nhà xây dựng riêng tư hoặc bị xóa và gọi ngầm các nhà xây dựng được đánh dấu rõ ràng.

Hai kết hợp đó có nghĩa là chúng an toàn hơn nếu những gì bên trong là hằng số nguyên thủy, nhưng kém an toàn hơn nếu chúng là các đối tượng (mặc dù đã được cố định trong C ++ 20)


Tôi đã thử xử lý xung quanh trên goldbolt.org để bỏ qua các nhà xây dựng "rõ ràng" hoặc "riêng tư" bằng cách sử dụng mã mẫu được cung cấp và tạo một hoặc riêng tư hoặc rõ ràng khác, và đã được thưởng bằng lỗi trình biên dịch thích hợp [s]. Muốn sao lưu với một số mã mẫu?
Mark Storer

Đây là cách khắc phục sự cố được đề xuất cho C ++ 20: open-std.org/jtc1/sc22/wg21/docs/ con / 2018 / p1008r1.pdf
Allan Jensen

1
Nếu bạn chỉnh sửa câu trả lời của mình để cho biết phiên bản C ++ nào bạn đang nói đến, tôi rất vui lòng thay đổi phiếu bầu của mình.
Mark Storer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.