2015-07-16 28 views
15

Xét đoạn mã sau:initialisation của std :: mảng <>

#include <array> 

struct A 
{ 
    int a; 
    int b; 
}; 

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

static std::array<A, 4> x2 = 
{ 
    { 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
    } 
}; 

static std::array<A, 4> x3 = 
{ 
     A{ 1, 2 }, 
     A{ 3, 4 }, 
     A{ 5, 6 }, 
     A{ 7, 8 } 
}; 

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

Biên soạn với gcc:

$ gcc -c --std=c++11 array.cpp 
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’ 
}; 
^ 
$ 

NB1: Bình luận ra tuyên bố khởi động đầu tiên, mã biên dịch mà không có lỗi.
NB2: Chuyển đổi tất cả các khởi tạo thành các hàm tạo hàm tạo ra cùng một kết quả.
NB3: MSVC2015 hoạt động giống nhau.

Tôi có thể thấy lý do tại sao khởi tạo đầu tiên không biên dịch được, và tại sao thứ hai và thứ ba là OK. (ví dụ: Xem C++11: Correct std::array initialization?.)

Câu hỏi của tôi là: Tại sao biên dịch khởi tạo cuối cùng lại chính xác?

+0

Tôi rất tiếc nhưng tôi không thể thấy tại sao bài tập đầu tiên không biên dịch được, bạn có thể cho tôi biết thêm không? Thật thú vị ! – Telokis

+1

@Ninetainedo - xem câu hỏi được liên kết. – Jeremy

+0

@dyp - Đã sửa. – Jeremy

Trả lời

27

Phiên bản ngắn: Một mệnh đề khởi tạo bắt đầu bằng { sẽ dừng cú đúp. Đây là trường hợp trong ví dụ đầu tiên với {1,2}, nhưng không phải trong ví dụ thứ ba và thứ tư sử dụng A{1,2}. Brace-elision tiêu thụ các mệnh đề khởi tạo N tiếp theo (trong đó N phụ thuộc vào tổng hợp được khởi tạo), đó là lý do tại sao chỉ mệnh đề khởi tạo đầu tiên của N phải không bắt đầu bằng {.


Trong tất cả các triển khai của Thư viện chuẩn C++ tôi biết, std::array là cấu trúc chứa mảng kiểu C. Nghĩa là, bạn có một tổng hợp, trong đó có một tiểu tổng, giống như

template<typename T, std::size_t N> 
struct array 
{ 
    T __arr[N]; // don't access this directly! 
}; 

Khi khởi tạo một std::array từ một chuẩn bị tinh thần-init-list, bạn sẽ do đó phải khởi tạo các thành viên của chứa mảng. Do đó, đối với những triển khai đó, biểu mẫu rõ ràng là:

std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }}; 

Bộ niềng ngoài cùng đề cập đến cấu trúc std::array; tập hợp dấu ngoặc kép thứ hai đề cập đến mảng kiểu C lồng nhau.


C++ cho phép khởi tạo tổng hợp để bỏ qua một số niềng răng khi khởi tạo tập hợp lồng nhau. Ví dụ:

struct outer { 
    struct inner { 
     int i; 
    }; 
    inner x; 
}; 

outer e = { { 42 } }; // explicit braces 
outer o = { 42 }; // with brace-elision 

Các quy tắc như sau (sử dụng một bản thảo sau N4527, đó là hậu C++ 14, nhưng C++ 11 chứa một khiếm khuyết liên quan đến dù sao đây):

Niềng răng có thể được ưu tiên trong danh sách khởi tạo như sau. Nếu số initializer-list bắt đầu bằng dấu ngoặc nhọn bên trái, thì danh sách được phân tách bằng dấu phẩy mới khởi tạo thành viên một phân nhóm; có sai khi có thêm điều khoản khởi tạo hơn thành viên.Tuy nhiên, nếu các initializer-list cho một subaggregate không bắt đầu bằng một cú đúp trái, sau đó chỉ đủ initializer-khoản từ danh sách được đưa ra nhằm khởi tạo thành viên của subaggregate; bất kỳ còn lại initializer-điều khoản là bên trái để khởi tạo thành viên tiếp theo của tổng hợp trong đó phân nhóm hiện tại là một thành viên.

Áp dụng điều này để các std::array -example đầu tiên:

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

này được hiểu như sau:

static std::array<A, 4> x1 = 
{  // x1 { 
    {  // __arr { 
    1, //  __arr[0] 
    2 //  __arr[1] 
     //  __arr[2] = {} 
     //  __arr[3] = {} 
    }  // } 

    {3,4}, // ?? 
    {5,6}, // ?? 
    ... 
};  // } 

Đầu tiên { được thực hiện như initializer của std::array struct. Các điều khoản khởi tạo {1,2}, {3,4} v.v ... sau đó được lấy làm bộ khởi tạo của các phân nhóm của std::array. Lưu ý rằng std::array chỉ có một phân tách đơn __arr. Vì mệnh đề khởi tạo đầu tiên{1,2} bắt đầu bằng {, ngoại lệ brace-elision không xảy ra và trình biên dịch cố gắng khởi tạo mảng A __arr[4] lồng nhau với {1,2}. Các điều khoản khởi tạo còn lại{3,4}, {5,6} v.v. không đề cập đến bất kỳ phân nhóm nào là std::array và do đó là bất hợp pháp.

Trong ví dụ thứ ba và thứ tư, người đầu tiên initializer-khoản cho subaggregate của std::arraykhông bắt đầu bằng một {, do đó nẹp sự bỏ bớt ngoại lệ được áp dụng:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

Vì vậy, nó là được hiểu như sau:

static std::array<A, 4> x4 = 
    {    // x4 { 
       // __arr {  -- brace elided 
    A{ 1, 2 }, //  __arr[0] 
    { 3, 4 }, //  __arr[1] 
    { 5, 6 }, //  __arr[2] 
    { 7, 8 } //  __arr[3] 
       // }    -- brace elided 
    };   // } 

Do đó, A{1,2} làm tất cả bốn initializer-điều khoản được sử dụng để khởi tạo mảng kiểu lồng nhau. Nếu bạn thêm một initializer:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 }, 
     X 
}; 

sau đó X này sẽ được sử dụng để khởi tạo subaggregate tiếp theo của std::array. Ví dụ.

struct outer { 
    struct inner { 
     int a; 
     int b; 
    }; 

    inner i; 
    int c; 
}; 

outer o = 
    {  // o { 
      // i { 
    1,  //  a 
    2,  //  b 
      // } 
    3  // c 
    };  // } 

Xoay vòng tiêu thụ các điều khoản khởi tạo N tiếp theo, trong đó N được xác định thông qua số lượng bộ khởi tạo cần thiết cho tổng hợp (phụ) được khởi tạo. Vì vậy, nó chỉ quan trọng hay không đầu tiên của những N-điều khoản khởi tạo bắt đầu với một {.

More tương tự như OP:

struct inner { 
    int a; 
    int b; 
}; 

struct outer { 
    struct middle { 
     inner i; 
    }; 

    middle m; 
    int c; 
}; 

outer o = 
    {    // o { 
       // m { 
    inner{1,2}, //  i 
       // } 
    3   // c 
    };    // } 

Lưu ý rằng cú đúp-sự bỏ bớt được áp dụng một cách đệ quy; chúng tôi thậm chí có thể viết nhầm lẫn

outer o = 
    {  // o { 
      // m { 
      //  i { 
    1,  //  a 
    2,  //  b 
      //  } 
      // } 
    3  // c 
    };  // } 

Nơi chúng tôi bỏ qua cả niềng răng cho o.mo.m.i. Hai mệnh đề khởi tạo đầu tiên được tiêu thụ để khởi tạo o.m.i, số còn lại khởi tạo o.c. Một khi chúng ta chèn một cặp niềng răng xung quanh 1,2, nó được hiểu là các cặp niềng răng tương ứng với o.m:

outer o = 
    {  // o { 
    {  // m { 
      //  i { 
     1, //  a 
     2, //  b 
      //  } 
    }  // } 
    3  // c 
    };  // } 

Ở đây, khởi tạo cho o.m không bắt đầu với một {, do đó brace-sự bỏ bớt không áp dụng. Bộ khởi tạo cho o.m.i1, không bắt đầu bằng số {, do đó brace-elision được áp dụng cho o.m.i và hai trình khởi tạo 12 được sử dụng.

+0

Câu trả lời hay, toàn diện. Cảm ơn! – Jeremy

+0

++ bình chọn, câu trả lời xuất sắc như bình thường. Một câu hỏi mặc dù: Đã có một bản nháp sau N4527? – Columbo

+0

@Columbo Vâng, không chính thức. Đó là từ repo github, mà làm cho nó không phải là một dự thảo làm việc chính thức, nhưng * vật liệu làm việc tạm thời *. – dyp

Các vấn đề liên quan