2015-02-09 25 views
22

Tôi không hiểu quy tắc khởi tạo cú đúp C++ 11 hoạt động như thế nào ở đây. Có mã này:C++ 11 hành vi khởi tạo dấu ngoặc kép lạ

struct Position_pod { 
    int x,y,z; 
}; 

class Position { 
public: 
    Position(int x=0, int y=0, int z=0):x(x),y(y),z(z){} 
    int x,y,z; 
}; 

struct text_descriptor { 
    int    id; 
    Position_pod pos; 
    const int  &constNum; 
}; 

struct text_descriptor td[3] = { 
    {0, {465,223}, 123}, 
    {1, {465,262}, 123}, 
}; 

int main() 
{ 
    return 0; 
} 

Lưu ý rằng mảng được khai báo có 3 phần tử, nhưng chỉ 2 bộ khởi tạo được cung cấp.

Tuy nhiên, nó biên dịch không có lỗi, âm thanh lạ, vì thành viên tham chiếu của thành phần mảng cuối cùng sẽ không được khởi tạo. Trên thực tế, nó có giá trị NULL:

(gdb) p td[2].constNum 
$2 = (const int &) @0x0: <error reading variable> 

Và bây giờ là "kỳ diệu": Tôi đã thay đổi Position_pod để Đặt

struct text_descriptor { 
    int    id; 
    Position_pod pos; 
    const int  &constNum; 
}; 

trở này:

struct text_descriptor { 
    int    id; 
    Position  pos; 
    const int  &constNum; 
}; 

và bây giờ nó mang lại cho các lỗi dự kiến :

error: uninitialized const member ‘text_descriptor::constNum' 

q của tôi uestion: Tại sao nó biên dịch trong trường hợp đầu tiên, khi nó nên đưa ra một lỗi (như trong trường hợp thứ hai). Sự khác biệt là, Position_pod sử dụng khởi tạo cú pháp kiểu C và vị trí sử dụng khởi tạo kiểu C++ 11, gọi hàm dựng của vị trí. Nhưng làm thế nào điều này ảnh hưởng đến khả năng để lại một thành viên tham khảo uninitialized?

(Cập nhật) Compiler: gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

+9

Phiên bản trình biên dịch là quan trọng. [Clang 3.5 và GCC 4.9.2] (http://coliru.stacked-crooked.com/a/4669040f8cc1bd3e) không biên dịch điều này. –

+1

Trông giống như một lỗi trình biên dịch. Tôi có thể tái tạo trên GCC 4.8 và trước đó, vì vậy có vẻ như nó đã được sửa trong 4.9. –

+1

MSVC 2013 cũng không biên dịch nó. – Drop

Trả lời

23

Nó sẽ được rõ ràng rằng

struct text_descriptor td[3] = { 
    {0, {465,223}, 123}, 
    {1, {465,262}, 123}, 
}; 

là danh sách-khởi động, và rằng danh sách initialiser là không có sản phẩm nào.

C++ 11 nói ([dcl.init.list] p3):

List-initialization of an object or reference of type T is defined as follows:

  • If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
  • ...

[dcl.init.aggr] p1:

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

td là một mảng, vì vậy nó là một tổng hợp, do đó, tổng hợp initialisation được thực hiện.

[dcl.init.aggr] p7:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list (8.5.4).

Đây là trường hợp ở đây, vì vậy td[2] được khởi tạo từ một danh sách initialiser trống rỗng, mà ([dcl.init.list] p3 một lần nữa) có nghĩa là nó được khởi tạo giá trị.

giá trị khởi động, đến lượt nó, nghĩa là ([dcl.init] p7):

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), ...
  • if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T 's implicitly-declared default constructor is non-trivial, that constructor is called.
  • ...

lớp học của bạn text_descriptor là một lớp không có constructor dùng cung cấp, vì vậy td[2] là lần đầu tiên zero-khởi tạo, và thì hàm tạo của nó được gọi.

phương tiện Zero-khởi động ([dcl.init] p5):

To zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), ...
  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
  • if T is a (possibly cv-qualified) union type, ...
  • if T is an array type, ...
  • if T is a reference type, no initialization is performed.

này được xác định rõ bất kể constructor mặc định text_descriptor 's: nó chỉ zero-initialises các thành viên phi tham khảo và phụ các thành viên.

Sau đó, hàm tạo mặc định được gọi, nếu nó không nhỏ. Đây là cách các nhà xây dựng mặc định được định nghĩa ([đặc biệt] p5):

A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X , a constructor having no parameters is implicitly declared as defaulted (8.4). An implicitly-declared default constructor is an inline public member of its class. A defaulted default constructor for class X is defined as deleted if:

  • ...
  • any non-static data member with no brace-or-equal-initializer is of reference type,
  • ...

A default constructor is trivial if it is not user-provided and if:

  • its class has no virtual functions (10.3) and no virtual base classes (10.1), and
  • no non-static data member of its class has a brace-or-equal-initializer, and
  • all the direct base classes of its class have trivial default constructors, and
  • for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.

Otherwise, the default constructor is non-trivial.

Vì vậy, các nhà xây dựng ngầm được xác định sẽ bị xóa, như mong đợi, nhưng nó cũng là tầm thường, nếu pos là một loại POD (!) . Bởi vì hàm tạo là tầm thường, nó không được gọi. Bởi vì các nhà xây dựng không được gọi là, thực tế là nó bị xóa không phải là một vấn đề.

Đây là lỗ hổng trong C++ 11, đã được sửa. Nó đã xảy ra đã được cố định để đối phó với inaccessible trivial default constructors, nhưng từ ngữ cố định cũng bao gồm các nhà thầu mặc định tầm thường đã bị xóa. N4140 (khoảng C++ 14) nói trong [dcl.init.aggr] p7 (tôi nhấn mạnh):

  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

Như T.C. chỉ ra trong các ý kiến, another DR cũng thay đổi để td[2] vẫn được khởi tạo từ một danh sách khởi tạo rỗng, nhưng danh sách initialiser trống bây giờ ngụ ý khởi tạo tổng hợp. Điều đó, đến lượt nó, ngụ ý rằng mỗi thành viên của td[2] được khởi tạo từ một danh sách khởi tạo rỗng ([dcl.init.aggr] p7 một lần nữa), vì vậy dường như khởi tạo thành viên tham chiếu từ {}.

[dcl.init.aggr] p9 sau đó nói (như remyabel đã chỉ ra trong một câu trả lời bây giờ bị xóa):

If an incomplete or empty initializer-list leaves a member of reference type uninitialized, the program is ill-formed.

Không rõ với tôi rằng điều này áp dụng đối với tài liệu tham khảo được khởi tạo từ ngầm {} , nhưng các trình biên dịch giải thích nó như vậy, và không có nhiều thứ khác có thể có nghĩa là nó.

+0

Tài liệu tham khảo không thể được khởi tạo giá trị. –

+0

@remyabel Tôi biết, nhưng giá trị khởi tạo của một lớp không hàm ý việc khởi tạo giá trị của các thành viên của lớp đó. – hvd

+0

@bogdan Nó có một hàm tạo mặc định. Hàm khởi tạo mặc định đó bị xóa, do đó, nó là một lỗi nếu hàm tạo mặc định đó được sử dụng, nhưng do một khoảng trống trong từ ngữ, nó thậm chí không được sử dụng. Tôi trích dẫn [đặc biệt] p5 từ tiêu chuẩn, trong đó xác định khi một constructor mặc định được định nghĩa, và làm thế nào nó được định nghĩa. – hvd

-1

Phiên bản đầu tiên (một với _pod suffix) vẫn hoạt động nhưng không đưa ra lỗi bởi vì đối với giá trị z, các giá trị mặc định của int được chọn (0). Idem cho tham chiếu const int.

Trong phiên bản thứ hai, bạn không thể xác định tham chiếu const mà không cho nó giá trị. Trình biên dịch cung cấp cho bạn một lỗi vì sau này bạn không thể gán bất kỳ giá trị nào cho nó.

Ngoài ra, trình biên dịch bạn đang sử dụng đóng vai trò quan trọng ở đây, có thể đó là lỗi, chỉ vì bạn đang khai báo thành viên lớp trước một thành viên int.

+2

Nhưng cả hai phiên bản (cố gắng) khởi tạo một tham chiếu mà không cho nó một giá trị. Câu hỏi là về số lượng initialisers cho mảng, không phải cho thành viên 'Position' của mỗi phần tử mảng. (Và để trả lời câu hỏi của bạn: để chứng minh hành vi của trình biên dịch thay đổi như thế nào nếu bạn thay đổi loại thành viên đó từ POD thành không phải POD.) –

+0

@MikeSeymour đó là một vấn đề về trình biên dịch. Và bạn cũng bao gồm cả một lớp trong cấu trúc. Sẽ tốt hơn nếu bạn giữ cấu trúc dễ dàng, hoặc có thể sử dụng hàm tạo trong cấu trúc đầu tiên – madduci

+1

"đó là một vấn đề trình biên dịch" - vì vậy nó sẽ có vẻ. Nhưng câu trả lời của bạn không nói điều đó, và dường như nói rằng phiên bản đầu tiên được hình thành tốt, mặc dù không phải vậy. –

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