Uẩn và POD là gì và làm thế nào / tại sao chúng đặc biệt?


548

Đây FAQ khoảng Uẩn và PODs và bìa các tài liệu sau đây:

  • Uẩn là gì?
  • Là gì POD s (Plain Old dữ liệu)?
  • Họ có liên quan với nhau như thê nào?
  • Làm thế nào và tại sao chúng đặc biệt?
  • Điều gì thay đổi cho C ++ 11?


Có thể nói rằng động lực đằng sau các định nghĩa này đại khái là: POD == memcpy'able, Aggregate == tổng hợp có thể khởi tạo?
Ofek Shilon

Câu trả lời:


572

Đọc thế nào:

Bài viết này khá dài. Nếu bạn muốn biết về cả tổng hợp và POD (Dữ liệu cũ đơn giản), hãy dành thời gian và đọc nó. Nếu bạn chỉ quan tâm đến tổng hợp, chỉ đọc phần đầu tiên. Nếu bạn chỉ quan tâm đến POD thì trước tiên bạn phải đọc định nghĩa, hàm ý và ví dụ về tổng hợp và sau đó bạn có thể chuyển sang POD nhưng tôi vẫn khuyên bạn nên đọc toàn bộ phần đầu tiên. Khái niệm tổng hợp là cần thiết để xác định POD. Nếu bạn tìm thấy bất kỳ lỗi nào (thậm chí là nhỏ, bao gồm ngữ pháp, kiểu dáng, định dạng, cú pháp, v.v.), vui lòng để lại nhận xét, tôi sẽ chỉnh sửa.

Câu trả lời này áp dụng cho C ++ 03. Đối với các tiêu chuẩn C ++ khác, xem:

Tập hợp là gì và tại sao chúng đặc biệt

Định nghĩa chính thức từ tiêu chuẩn C ++ ( C ++ 03 8.5.1 §1 ) :

Tập hợp là một mảng hoặc một lớp (mệnh đề 9) không có hàm tạo do người dùng khai báo (12.1), không có thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ (mệnh đề 11), không có lớp cơ sở (mệnh đề 10) và không có hàm ảo (10.3 ).

Vì vậy, OK, hãy phân tích định nghĩa này. Trước hết, bất kỳ mảng là một tổng hợp. Một lớp cũng có thể là một tổng hợp nếu chờ đợi! không có gì được nói về cấu trúc hoặc công đoàn, chúng không thể là tập hợp? Vâng, họ có thể. Trong C ++, thuật ngữ này classđề cập đến tất cả các lớp, cấu trúc và công đoàn. Vì vậy, một lớp (hoặc struct, hoặc union) là một tổng hợp khi và chỉ khi nó thỏa mãn các tiêu chí từ các định nghĩa trên. Những tiêu chí này ngụ ý gì?

  • Điều này không có nghĩa là một lớp tổng hợp không thể có các hàm tạo, trên thực tế, nó có thể có một hàm tạo mặc định và / hoặc một hàm tạo sao chép miễn là chúng được trình biên dịch khai báo ngầm và không được người dùng trình bày rõ ràng

  • Không có thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ . Bạn có thể có nhiều hàm thành viên riêng tư và được bảo vệ (nhưng không phải hàm tạo) cũng như nhiều hàm thành viên dữ liệu tĩnh riêng tư hoặc được bảo vệ và các hàm thành viên tùy thích và không vi phạm các quy tắc cho các lớp tổng hợp

  • Một lớp tổng hợp có thể có một toán tử gán-sao chép do người dùng khai báo / người dùng định nghĩa và / hoặc hàm hủy

  • Một mảng là một tổng hợp ngay cả khi nó là một mảng của loại lớp không tổng hợp.

Bây giờ hãy xem một số ví dụ:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

Bạn có được ý tưởng. Bây giờ hãy xem tổng hợp là đặc biệt như thế nào. Chúng, không giống như các lớp không tổng hợp, có thể được khởi tạo với dấu ngoặc nhọn {}. Cú pháp khởi tạo này thường được biết đến với các mảng và chúng ta chỉ biết rằng đây là các tổng hợp. Vì vậy, hãy bắt đầu với họ.

Type array_name[n] = {a1, a2, …, am};

if (m == n) phần tử thứ
icủa mảng được khởi tạo với i khác if (m <n) các phần tử m đầu tiên của mảng được khởi tạo với các phần tử 1 , a 2 , Lỗi, a m và cácphần tửkháclà, nếu có thể, được khởi tạo giá trị (xem bên dưới để giải thích thuật ngữ) khác nếu (m> n) trình biên dịch sẽ phát sinh lỗi khác (đây là trường hợp khi n không được chỉ định như thế nào ) kích thước của mảng (n) được giả sử là bằng m, do đótương đương với

n - m


int a[] = {1, 2, 3};
int a[] = {1, 2, 3};int a[3] = {1, 2, 3};

Khi một đối tượng kiểu vô hướng ( bool, int, char, double, con trỏ, vv) là giá trị khởi tạo nó có nghĩa là nó được khởi tạo với 0cho loại đó ( falsecho bool, 0.0cho double, vv). Khi một đối tượng của kiểu lớp với hàm tạo mặc định do người dùng khai báo được khởi tạo giá trị, hàm tạo mặc định của nó được gọi. Nếu hàm tạo mặc định được định nghĩa ngầm định thì tất cả các thành viên không phải là thành viên được khởi tạo giá trị đệ quy. Định nghĩa này không chính xác và một chút không chính xác nhưng nó sẽ cung cấp cho bạn ý tưởng cơ bản. Một tham chiếu không thể được khởi tạo giá trị. Khởi tạo giá trị cho một lớp không tổng hợp có thể thất bại nếu, ví dụ, lớp không có hàm tạo mặc định thích hợp.

Ví dụ về khởi tạo mảng:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

Bây giờ hãy xem làm thế nào các lớp tổng hợp có thể được khởi tạo với dấu ngoặc nhọn. Khá nhiều cách tương tự. Thay vì các phần tử mảng, chúng tôi sẽ khởi tạo các thành viên dữ liệu không tĩnh theo thứ tự xuất hiện của chúng trong định nghĩa lớp (tất cả chúng đều công khai theo định nghĩa). Nếu có ít bộ khởi tạo hơn thành viên, phần còn lại được khởi tạo giá trị. Nếu không thể khởi tạo giá trị một trong những thành viên không được khởi tạo rõ ràng, chúng ta sẽ gặp lỗi thời gian biên dịch. Nếu có nhiều bộ khởi tạo hơn mức cần thiết, chúng ta cũng sẽ gặp lỗi thời gian biên dịch.

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

Trong ví dụ trên y.cđược khởi tạo với 'a', y.x.i1với 10, y.x.i2với 20, y.i[0]với 20, y.i[1]với 30y.fđược khởi tạo giá trị, nghĩa là, được khởi tạo với 0.0. Các thành viên tĩnh được bảo vệ hoàn toàn dkhông được khởi tạo, bởi vì nó là static.

Công đoàn tổng hợp khác nhau ở chỗ bạn chỉ có thể khởi tạo thành viên đầu tiên của họ bằng niềng răng. Tôi nghĩ rằng nếu bạn đủ tiến bộ trong C ++ để thậm chí cân nhắc sử dụng các công đoàn (việc sử dụng chúng có thể rất nguy hiểm và phải được suy nghĩ cẩn thận), bạn có thể tự mình tìm kiếm các quy tắc cho các công đoàn trong tiêu chuẩn :).

Bây giờ chúng ta đã biết những gì đặc biệt về tổng hợp, chúng ta hãy cố gắng hiểu các hạn chế đối với các lớp học; đó là lý do tại sao họ ở đó Chúng ta nên hiểu rằng khởi tạo theo từng thành viên với dấu ngoặc nhọn ngụ ý rằng lớp không có gì khác hơn tổng của các thành viên. Nếu có một hàm tạo do người dùng định nghĩa, điều đó có nghĩa là người dùng cần thực hiện một số công việc bổ sung để khởi tạo các thành viên do đó việc khởi tạo dấu ngoặc sẽ không chính xác. Nếu có các hàm ảo, điều đó có nghĩa là các đối tượng của lớp này có (trên hầu hết các cài đặt) một con trỏ tới cái gọi là vtable của lớp, được đặt trong hàm tạo, do đó khởi tạo dấu ngoặc sẽ không đủ. Bạn có thể tìm ra phần còn lại của các hạn chế theo cách tương tự như một bài tập :).

Thế là đủ về các uẩn. Bây giờ chúng ta có thể định nghĩa một tập hợp các loại chặt chẽ hơn, để dí dỏm, PODs

POD là gì và tại sao chúng đặc biệt

Định nghĩa chính thức từ tiêu chuẩn C ++ ( C ++ 03 9 §4 ) :

POD-struct là một lớp tổng hợp không có các thành viên dữ liệu không tĩnh thuộc loại không phải POD-struct, non-POD-union (hoặc mảng của các loại đó) hoặc không có toán tử gán sao chép do người dùng định nghĩa và không có toán tử gán hàm hủy do người dùng định nghĩa. Tương tự, POD-union là một liên minh tổng hợp không có thành viên dữ liệu không tĩnh thuộc loại không POD-struct, non-POD-union (hoặc mảng của các loại đó) hoặc không có toán tử gán sao chép do người dùng định nghĩa và không có hàm hủy do người dùng định nghĩa. Một lớp POD là một lớp là POD-struct hoặc POD-union.

Wow, cái này khó hơn để phân tích, phải không? :) Chúng ta hãy để các công đoàn ra ngoài (với cùng lý do như trên) và viết lại theo cách rõ ràng hơn một chút:

Một lớp tổng hợp được gọi là POD nếu nó không có toán tử và hàm hủy gán sao chép do người dùng định nghĩa và không có thành viên không phải là thành viên nào của nó là lớp không POD, mảng không phải POD hoặc tham chiếu.

Định nghĩa này có nghĩa là gì? (Tôi có đề cập đến POD là viết tắt của Plain Old Data không?)

  • Tất cả các lớp POD là tập hợp, hoặc, nói cách khác, nếu một lớp không phải là tổng hợp thì chắc chắn đó không phải là POD
  • Các lớp, giống như các cấu trúc, có thể là POD mặc dù thuật ngữ tiêu chuẩn là POD-struct cho cả hai trường hợp
  • Giống như trong trường hợp tổng hợp, không có vấn đề gì với các thành viên tĩnh mà lớp có

Ví dụ:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

Các lớp POD, các hiệp hội POD, các kiểu vô hướng và các mảng của các kiểu đó được gọi chung là các kiểu POD.
POD là đặc biệt theo nhiều cách. Tôi sẽ chỉ cung cấp một số ví dụ.

  • Các lớp POD là gần nhất với các cấu trúc C. Không giống như chúng, các POD có thể có các hàm thành viên và các thành viên tĩnh tùy ý, nhưng cả hai đều không thay đổi bố cục bộ nhớ của đối tượng. Vì vậy, nếu bạn muốn viết một thư viện động di động nhiều hơn hoặc ít hơn có thể được sử dụng từ C và thậm chí .NET, bạn nên cố gắng làm cho tất cả các hàm xuất của bạn mất và chỉ trả về các tham số của loại POD.

  • Thời gian tồn tại của các đối tượng của loại lớp không POD bắt đầu khi hàm tạo kết thúc và kết thúc khi hàm hủy kết thúc. Đối với các lớp POD, thời gian tồn tại bắt đầu khi lưu trữ cho đối tượng bị chiếm dụng và kết thúc khi lưu trữ đó được giải phóng hoặc tái sử dụng.

  • Đối với các đối tượng thuộc loại POD, tiêu chuẩn được đảm bảo rằng khi bạn memcpyđưa nội dung của đối tượng vào một mảng char hoặc unsign char, và sau đó memcpynội dung trở lại đối tượng của bạn, đối tượng sẽ giữ giá trị ban đầu của nó. Xin lưu ý rằng không có sự đảm bảo nào cho các đối tượng thuộc loại không POD. Ngoài ra, bạn có thể sao chép một cách an toàn các đối tượng POD với memcpy. Ví dụ sau đây giả sử T là loại POD:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
  • tuyên bố goto. Như bạn có thể biết, đó là bất hợp pháp (trình biên dịch sẽ phát sinh lỗi) để thực hiện một bước nhảy qua goto từ một điểm mà một số biến chưa có trong phạm vi đến một điểm mà nó đã ở trong phạm vi. Hạn chế này chỉ áp dụng nếu biến thuộc loại không phải POD. Trong ví dụ sau đây f()là không định hình trong khi g()được hình thành tốt. Lưu ý rằng trình biên dịch của Microsoft quá tự do với quy tắc này, nó chỉ đưa ra cảnh báo trong cả hai trường hợp.

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
  • Nó được đảm bảo rằng sẽ không có phần đệm trong phần đầu của đối tượng POD. Nói cách khác, nếu thành viên đầu tiên của lớp POD loại A, bạn có thể an toàn reinterpret_casttừ A*đến T*và lấy con trỏ đến thành viên đầu tiên và ngược lại.

Danh sách này tiếp tục và trên mạng

Phần kết luận

Điều quan trọng là phải hiểu chính xác POD là gì bởi vì nhiều tính năng ngôn ngữ, như bạn thấy, hành xử khác nhau đối với chúng.


3
Câu trả lời tốt đẹp. Nhận xét: "Nếu hàm tạo mặc định được xác định ngầm định thì tất cả các thành viên không phải là thành viên được khởi tạo giá trị đệ quy." và "Khởi tạo giá trị cho một lớp không tổng hợp có thể thất bại nếu, ví dụ, lớp không có hàm tạo mặc định phù hợp." là không chính xác: Khởi tạo giá trị của một lớp với một hàm tạo mặc định được khai báo ngầm định không cần đến một hàm tạo mặc định được định nghĩa ngầm định. Do đó, được đưa ra (chèn private:khi thích hợp): struct A { int const a; };sau đó A()được định dạng tốt, ngay cả khi Ađịnh nghĩa hàm tạo mặc định của nó sẽ không được định dạng.
Julian Schaub - litb

4
@Kev: Nếu bạn quản lý để đóng gói thông tin tương tự vào một câu trả lời ngắn hơn, tất cả chúng ta sẽ vui vẻ bỏ phiếu!
sbi

3
@Armen cũng lưu ý bạn có thể thực hiện nhiều câu trả lời cho cùng một câu hỏi. Mỗi câu trả lời có thể chứa một phần của giải pháp cho câu hỏi. Theo ý kiến ​​của tôi, hãy chấp nhận điều đó - chấp nhận :)
Julian Schaub - litb

3
Trả lời là tuyệt vời. Tôi vẫn xem lại bài đăng này một số lần. Nhân tiện về cảnh báo cho Visual Studio. "Tuyên bố goto" cho pod đi kèm với sự thiếu hiểu biết về trình biên dịch MSVC như bạn đã đề cập. Nhưng đối với câu lệnh switch / case nó tạo ra lỗi biên dịch. Dựa trên khái niệm này, tôi đã thực hiện một số kiểm tra-pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/
bruziuz

2
Trong dấu đầu dòng bắt đầu bằng "Thời gian tồn tại của các đối tượng của loại lớp không POD bắt đầu khi hàm tạo kết thúc và kết thúc khi hàm hủy kết thúc." thay vào đó, phần cuối cùng sẽ nói "khi bộ hủy bắt đầu."
Quokka

457

Điều gì thay đổi cho C ++ 11?

Cốt liệu

Định nghĩa chuẩn của một tổng hợp đã thay đổi một chút, nhưng nó vẫn khá giống nhau:

Tập hợp là một mảng hoặc một lớp (Khoản 9) không có hàm tạo do người dùng cung cấp (12.1), không có bộ khởi tạo dấu ngoặc hoặc bằng cho các thành viên dữ liệu không tĩnh (9.2), không có thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ ( Khoản 11), không có lớp cơ sở (Khoản 10) và không có chức năng ảo (10.3).

Ok, có gì thay đổi?

  1. Trước đây, một tổng hợp có thể không có các hàm tạo do người dùng khai báo , nhưng bây giờ nó không thể có các hàm tạo do người dùng cung cấp . Có sự khác biệt? Vâng, có, bởi vì bây giờ bạn có thể khai báo các hàm tạo và mặc định chúng:

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };

    Đây vẫn là một tổng hợp vì một hàm tạo (hoặc bất kỳ hàm thành viên đặc biệt nào) được mặc định trên khai báo đầu tiên không do người dùng cung cấp.

  2. Bây giờ một tập hợp không thể có bất kỳ khởi tạo giằng hoặc bằng nhau cho các thành viên dữ liệu không tĩnh. Điều đó có nghĩa là gì? Chà, điều này chỉ là vì với tiêu chuẩn mới này, chúng ta có thể khởi tạo các thành viên trực tiếp trong lớp như thế này:

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };

    Sử dụng tính năng này làm cho lớp không còn là tổng hợp vì về cơ bản nó tương đương với việc cung cấp hàm tạo mặc định của riêng bạn.

Vì vậy, tổng hợp là gì đã không thay đổi nhiều. Đó vẫn là cùng một ý tưởng cơ bản, thích nghi với các tính năng mới.

Còn POD thì sao?

POD đã trải qua rất nhiều thay đổi. Rất nhiều quy tắc trước đây về POD được nới lỏng trong tiêu chuẩn mới này và cách định nghĩa được cung cấp trong tiêu chuẩn đã được thay đổi hoàn toàn.

Ý tưởng của POD là nắm bắt cơ bản hai thuộc tính riêng biệt:

  1. Nó hỗ trợ khởi tạo tĩnh và
  2. Biên dịch POD trong C ++ cung cấp cho bạn bố cục bộ nhớ giống như cấu trúc được biên dịch trong C.

Bởi vì điều này, định nghĩa đã được chia thành hai khái niệm riêng biệt: các lớp tầm thường và các lớp bố cục tiêu chuẩn , bởi vì các định nghĩa này hữu ích hơn POD. Các tiêu chuẩn hiện nay hiếm khi sử dụng POD hạn, thích cụ thể hơn không đáng kểtiêu chuẩn bố trí các khái niệm.

Định nghĩa mới về cơ bản nói rằng POD là một lớp vừa tầm thường vừa có bố cục tiêu chuẩn và thuộc tính này phải giữ đệ quy cho tất cả các thành viên dữ liệu không tĩnh:

Cấu trúc POD là một lớp không liên kết vừa là lớp tầm thường vừa là lớp bố cục tiêu chuẩn và không có thành viên dữ liệu không tĩnh của loại cấu trúc không POD, liên kết không POD (hoặc mảng của các loại đó). Tương tự, liên minh POD là một liên minh vừa là lớp tầm thường vừa là lớp bố cục tiêu chuẩn và không có thành viên dữ liệu không tĩnh của loại cấu trúc không POD, liên kết không POD (hoặc mảng của các loại đó). Một lớp POD là một lớp có cấu trúc POD hoặc liên kết POD.

Chúng ta hãy đi qua từng chi tiết của hai thuộc tính này một cách chi tiết.

Các lớp học tầm thường

Trivial là thuộc tính đầu tiên được đề cập ở trên: các lớp tầm thường hỗ trợ khởi tạo tĩnh. Nếu một lớp có thể sao chép một cách tầm thường (một siêu lớp của các lớp tầm thường), bạn có thể sao chép đại diện của nó ở nơi đó với những thứ như memcpyvà mong đợi kết quả là như nhau.

Tiêu chuẩn định nghĩa một lớp tầm thường như sau:

Một lớp có thể sao chép tầm thường là một lớp:

- không có các hàm tạo sao chép không tầm thường (12.8),

- không có các hàm tạo di chuyển không tầm thường (12.8),

- không có toán tử gán bản sao không tầm thường (13.5.3, 12.8),

- không có toán tử gán chuyển động không tầm thường (13.5.3, 12.8) và

- có một hàm hủy tầm thường (12.4).

Một lớp tầm thường là một lớp có hàm tạo mặc định tầm thường (12.1) và có thể sao chép một cách tầm thường.

[ Lưu ý: Đặc biệt, một lớp tầm thường có thể sao chép hoặc tầm thường không có chức năng ảo hoặc lớp cơ sở ảo. -End note ]

Vì vậy, tất cả những điều tầm thường và không tầm thường là gì?

Một constructor sao chép / di chuyển cho lớp X là tầm thường nếu nó không do người dùng cung cấp và nếu

- lớp X không có chức năng ảo (10.3) và không có lớp cơ sở ảo (10.1) và

- hàm tạo được chọn để sao chép / di chuyển từng tiểu dự án lớp cơ sở trực tiếp là tầm thường và

- đối với mỗi thành viên dữ liệu không tĩnh của X thuộc loại lớp (hoặc mảng của chúng), hàm tạo được chọn để sao chép / di chuyển thành viên đó là tầm thường;

mặt khác, constructor sao chép / di chuyển là không tầm thường.

Về cơ bản, điều này có nghĩa là một hàm tạo sao chép hoặc di chuyển là tầm thường nếu nó không do người dùng cung cấp, lớp không có gì ảo trong đó và thuộc tính này giữ đệ quy cho tất cả các thành viên của lớp và cho lớp cơ sở.

Định nghĩa của một toán tử gán / sao chép di chuyển tầm thường rất giống nhau, chỉ đơn giản là thay thế từ "constructor" bằng "toán tử gán".

Một hàm hủy tầm thường cũng có một định nghĩa tương tự, với các ràng buộc được thêm vào rằng nó không thể là ảo.

Và còn có một quy tắc tương tự khác tồn tại đối với các hàm tạo mặc định tầm thường, với việc bổ sung rằng hàm tạo mặc định là không tầm thường nếu lớp có các thành viên dữ liệu không tĩnh với các bộ khởi tạo giằng hoặc bằng nhau , mà chúng ta đã thấy ở trên.

Dưới đây là một số ví dụ để xóa mọi thứ:

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

Bố cục chuẩn

Bố trí tiêu chuẩn là tài sản thứ hai. Các tiêu chuẩn đề cập rằng những điều này hữu ích cho việc giao tiếp với các ngôn ngữ khác và đó là vì một lớp bố cục tiêu chuẩn có cùng bố cục bộ nhớ của cấu trúc hoặc liên kết C tương đương.

Đây là một tài sản khác phải giữ đệ quy cho các thành viên và tất cả các lớp cơ sở. Và như thường lệ, không có chức năng ảo hoặc lớp cơ sở ảo nào được phép. Điều đó sẽ làm cho bố cục không tương thích với C.

Một quy tắc thoải mái ở đây là các lớp bố cục tiêu chuẩn phải có tất cả các thành viên dữ liệu không tĩnh với cùng một điều khiển truy cập. Trước đây những điều này phải được công khai , nhưng bây giờ bạn có thể làm cho họ tin hoặc bảo vệ, miễn là họ tất cả tin hoặc tất cả được bảo vệ.

Khi sử dụng tính kế thừa, chỉ một lớp trong toàn bộ cây thừa kế có thể có các thành viên dữ liệu không tĩnh và thành viên dữ liệu không tĩnh đầu tiên không thể thuộc loại lớp cơ sở (điều này có thể phá vỡ các quy tắc bí danh), nếu không, nó không phải là một tiêu chuẩn- lớp bố trí.

Đây là cách định nghĩa trong văn bản tiêu chuẩn:

Một lớp bố trí tiêu chuẩn là một lớp:

- không có thành viên dữ liệu không tĩnh của loại bố cục không chuẩn (hoặc mảng của các loại đó) hoặc tham chiếu,

- không có chức năng ảo (10.3) và không có lớp cơ sở ảo (10.1),

- có cùng kiểm soát truy cập (Điều 11) cho tất cả các thành viên dữ liệu không tĩnh,

- không có các lớp cơ sở bố trí không chuẩn,

- hoặc không có thành viên dữ liệu không tĩnh trong lớp dẫn xuất nhất và nhiều nhất là một lớp cơ sở có thành viên dữ liệu không tĩnh hoặc không có lớp cơ sở có thành viên dữ liệu không tĩnh và

- không có các lớp cơ sở cùng loại với thành viên dữ liệu không tĩnh đầu tiên.

Một cấu trúc bố cục tiêu chuẩn là một lớp bố trí tiêu chuẩn được xác định với cấu trúc khóa lớp hoặc lớp khóa-lớp.

Liên kết bố cục tiêu chuẩn là một lớp bố trí tiêu chuẩn được xác định với liên kết khóa lớp.

[ Lưu ý: Các lớp bố cục tiêu chuẩn rất hữu ích để giao tiếp với mã được viết bằng các ngôn ngữ lập trình khác. Bố cục của chúng được chỉ định trong 9.2. -End note ]

Và hãy xem một vài ví dụ.

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

Phần kết luận

Với các quy tắc mới này, rất nhiều loại có thể là POD bây giờ. Và ngay cả khi một loại không phải là POD, chúng ta có thể tận dụng một số thuộc tính POD riêng biệt (nếu đó chỉ là một trong những bố cục tầm thường hoặc tiêu chuẩn).

Thư viện chuẩn có các đặc điểm để kiểm tra các thuộc tính này trong tiêu đề <type_traits>:

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;

2
bạn có thể vui lòng xây dựng các quy tắc sau: a) các lớp bố trí chuẩn phải có tất cả các thành viên dữ liệu không tĩnh với cùng một điều khiển truy cập; b) chỉ một lớp trong toàn bộ cây thừa kế có thể có các thành viên dữ liệu không tĩnh và thành viên dữ liệu không tĩnh đầu tiên không thể thuộc loại lớp cơ sở (điều này có thể phá vỡ các quy tắc bí danh). Đặc biệt những lý do cho họ là gì? Đối với quy tắc sau này, bạn có thể cung cấp một ví dụ về phá vỡ răng cưa không?
Andriy Tylychko

@AndyT: Xem câu trả lời của tôi. Tôi đã cố gắng trả lời theo kiến ​​thức tốt nhất của tôi.
Nicol Bolas

5
Có thể muốn cập nhật điều này cho C ++ 14, loại bỏ yêu cầu "không có dấu ngoặc kép hoặc khởi tạo bằng" cho các tổng hợp.
TC

@TC cảm ơn vì đã ngẩng cao đầu. Tôi sẽ tìm kiếm những thay đổi đó sớm và cập nhật nó.
R. Martinho Fernandes

1
Về bí danh: Có một quy tắc bố cục C ++ rằng nếu một lớp C có cơ sở X (trống) và thành viên dữ liệu đầu tiên của C là loại X, thì thành viên đầu tiên đó không thể ở cùng mức bù với cơ sở X; nó sẽ có một byte đệm giả trước nó, nếu cần để tránh điều đó. Có hai phiên bản X (hoặc lớp con) tại cùng một địa chỉ có thể phá vỡ những thứ cần phân biệt các thể hiện khác nhau thông qua địa chỉ của chúng (một phiên bản trống không có gì khác để phân biệt nó ...). Trong mọi trường hợp, cần phải đặt trong đó phần đệm byte phá vỡ 'bố cục tương thích'.
greggo

106

Điều gì đã thay đổi cho C ++ 14

Chúng ta có thể tham khảo tiêu chuẩn Dự thảo C ++ 14 để tham khảo.

Cốt liệu

Điều này được đề cập trong phần 8.5.1 Uẩn cho chúng ta định nghĩa sau:

Tập hợp là một mảng hoặc một lớp (Khoản 9) không có hàm tạo do người dùng cung cấp (12.1), không có thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ (Khoản 11), không có lớp cơ sở (Khoản 10) và không có chức năng ảo (10.3 ).

Thay đổi duy nhất bây giờ là thêm các trình khởi tạo thành viên trong lớp không làm cho một lớp không thành tập hợp. Vì vậy, ví dụ sau từ khởi tạo tổng hợp C ++ 11 cho các lớp có trình khởi tạo theo tốc độ thành viên :

struct A
{
  int a = 3;
  int b = 3;
};

không phải là tổng hợp trong C ++ 11 nhưng nó là trong C ++ 14. Thay đổi này được đề cập trong N3605: Công cụ khởi tạo và tổng hợp thành viên , có tóm tắt sau:

Bjarne Stroustrup và Richard Smith đã đưa ra một vấn đề về khởi tạo tổng hợp và khởi tạo thành viên không hoạt động cùng nhau. Bài viết này đề xuất khắc phục vấn đề bằng cách áp dụng từ ngữ được đề xuất của Smith nhằm loại bỏ một hạn chế mà các tập hợp không thể có người khởi tạo thành viên.

POD giữ nguyên

Định nghĩa cho cấu trúc POD ( dữ liệu cũ đơn giản ) được trình bày trong phần 9 Các lớp có nội dung:

POD struct 110 là một lớp không liên kết vừa là lớp tầm thường vừa là lớp bố cục tiêu chuẩn và không có thành viên dữ liệu không tĩnh của loại cấu trúc không POD, liên kết không POD (hoặc mảng của các loại đó). Tương tự, liên minh POD là một liên minh vừa là lớp tầm thường vừa là lớp bố cục tiêu chuẩn và không có thành viên dữ liệu không tĩnh của loại cấu trúc không POD, liên kết không POD (hoặc mảng của các loại đó). Một lớp POD là một lớp có cấu trúc POD hoặc liên kết POD.

đó là từ ngữ tương tự như C ++ 11.

Thay đổi bố cục tiêu chuẩn cho C ++ 14

Như đã đề cập trong các ý kiến pod dựa vào định nghĩa của tiêu chuẩn bố trí và điều đó đã làm thay đổi cho C ++ 14 nhưng điều này là thông qua báo cáo khiếm khuyết đã được áp dụng cho C ++ 14 sau khi thực tế.

Có ba DR:

Vì vậy, bố cục tiêu chuẩn đã đi từ Pre C ++ 14 này:

Một lớp bố trí tiêu chuẩn là một lớp:

  • (7.1) không có thành viên dữ liệu không tĩnh thuộc loại bố cục không chuẩn (hoặc mảng của các loại đó) hoặc tham chiếu,
  • (7.2) không có chức năng ảo ([class.virtual]) và không có lớp cơ sở ảo ([class.mi]),
  • (7.3) có cùng kiểm soát truy cập (Điều khoản [class.access]) cho tất cả các thành viên dữ liệu không tĩnh,
  • (7.4) không có các lớp cơ sở bố trí không chuẩn,
  • (7.5) hoặc không có thành viên dữ liệu không tĩnh trong lớp dẫn xuất nhất và nhiều nhất là một lớp cơ sở có thành viên dữ liệu không tĩnh hoặc không có lớp cơ sở có thành viên dữ liệu không tĩnh và
  • (7.6) không có lớp cơ sở cùng loại với thành viên dữ liệu không tĩnh đầu tiên.109

Về điều này trong C ++ 14 :

Một lớp S là một lớp bố trí tiêu chuẩn nếu nó:

  • (3.1) không có thành viên dữ liệu không tĩnh thuộc loại bố cục không chuẩn (hoặc mảng của các loại đó) hoặc tham chiếu,
  • (3.2) không có chức năng ảo và không có lớp cơ sở ảo,
  • (3.3) có cùng kiểm soát truy cập cho tất cả các thành viên dữ liệu không tĩnh,
  • (3.4) không có các lớp cơ sở bố trí không chuẩn,
  • (3.5) có nhiều nhất một tiểu nhóm lớp cơ sở của bất kỳ loại đã cho nào,
  • (3.6) có tất cả các thành viên dữ liệu không tĩnh và các trường bit trong lớp và các lớp cơ sở của nó trước tiên được khai báo trong cùng một lớp và
  • (3.7) không có phần tử của tập hợp M (S) của các loại làm lớp cơ sở, trong đó với bất kỳ loại X, M (X) được định nghĩa như sau.104 [Lưu ý: M (X) là tập hợp các loại tất cả các tiểu dự án không thuộc lớp cơ sở có thể ở độ lệch 0 trong X. - ghi chú cuối]
    • (3.7.1) Nếu X là loại lớp không liên kết không có thành viên dữ liệu không tĩnh (có thể được kế thừa), thì tập M (X) trống.
    • (3.7.2) Nếu X là loại lớp không liên kết có thành viên dữ liệu không tĩnh thuộc loại X0 có kích thước bằng 0 hoặc là thành viên dữ liệu không tĩnh đầu tiên của X (trong đó thành viên có thể là liên kết ẩn danh ), tập M (X) bao gồm X0 và ​​các phần tử của M (X0).
    • (3.7.3) Nếu X là loại kết hợp, tập hợp M (X) là liên kết của tất cả M (Ui) và tập hợp chứa tất cả Ui, trong đó mỗi Ui là loại thành viên dữ liệu không tĩnh thứ i của X .
    • (3.7.4) Nếu X là kiểu mảng có kiểu phần tử Xe, tập M (X) bao gồm Xe và các phần tử của M (Xe).
    • (3.7.5) Nếu X là loại không có lớp, không có mảng, tập M (X) trống.

4
Có một đề xuất cho phép các tập hợp có một lớp cơ sở miễn là nó có thể xây dựng được mặc định, xem N4404
Shafik Yaghmour

trong khi POD có thể giữ nguyên, C ++ 14 StandardLayoutType, mà là một yêu cầu đối với POD, đã thay đổi theo cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli 改造 心 心 cảm ơn bạn, tôi không biết tôi đã bỏ lỡ những điều đó như thế nào, tôi sẽ cố gắng cập nhật trong vài ngày tới.
Shafik Yaghmour

Hãy cho tôi biết nếu bạn có thể đưa ra một ví dụ là POD trong C ++ 14 nhưng không phải trong C ++ 11 :-) Tôi đã bắt đầu một danh sách chi tiết các ví dụ tại: stackoverflow.com/questions/146452/what- are-pod-type-in-c / Sự
Ciro Santilli 冠状 病 六四

1
@CiroSantilli 改造 心 心 法轮功 vì vậy điều xảy ra ở đây là nếu chúng ta xem mô tả bố cục tiêu chuẩn trong C ++ 11C ++ 14 mà chúng khớp. Những thay đổi này được áp dụng thông qua các báo cáo lỗi trở lại C ++ 14. Vì vậy, khi tôi viết điều này ban đầu, nó đã đúng :-p
Shafik Yaghmour

47

bạn có thể vui lòng xây dựng các quy tắc sau đây:

Tôi sẽ thử:

a) các lớp bố trí chuẩn phải có tất cả các thành viên dữ liệu không tĩnh với cùng một điều khiển truy cập

Đó là đơn giản: tất cả các thành viên dữ liệu tĩnh không phải tất cả được public, privatehoặc protected. Bạn không thể có một số publicvà một số private.

Lý do cho họ đi đến lý do để có sự phân biệt giữa "bố cục tiêu chuẩn" và "không bố trí tiêu chuẩn". Cụ thể, để cho trình biên dịch tự do lựa chọn cách đưa mọi thứ vào bộ nhớ. Nó không chỉ là về con trỏ vtable.

Quay lại khi họ chuẩn hóa C ++ vào năm 98, về cơ bản họ phải dự đoán cách mọi người sẽ thực hiện nó. Mặc dù họ có khá nhiều kinh nghiệm triển khai với nhiều hương vị khác nhau của C ++, nhưng họ không chắc chắn về mọi thứ. Vì vậy, họ quyết định thận trọng: cung cấp cho các trình biên dịch càng nhiều tự do càng tốt.

Đó là lý do tại sao định nghĩa về POD trong C ++ 98 rất nghiêm ngặt. Nó đã cho trình biên dịch C ++ vĩ độ lớn về bố trí thành viên cho hầu hết các lớp. Về cơ bản, các loại POD được dự định là trường hợp đặc biệt, một cái gì đó bạn đặc biệt viết vì một lý do.

Khi C ++ 11 đang được làm việc, họ có nhiều kinh nghiệm hơn với trình biên dịch. Và họ nhận ra rằng ... các nhà văn trình biên dịch C ++ thực sự lười biếng. Họ có tất cả sự tự do này, nhưng họ không làm gì với nó.

Các quy tắc của bố cục tiêu chuẩn ít nhiều được mã hóa thông lệ: hầu hết các trình biên dịch thực sự không phải thay đổi nhiều nếu có bất cứ điều gì để thực hiện chúng (bên ngoài có thể là một số thứ cho các đặc điểm loại tương ứng).

Bây giờ, khi nó đến public/ private, mọi thứ đã khác. Quyền tự do sắp xếp lại mà thành viên là publicvs.private thực tế có thể quan trọng đối với trình biên dịch, đặc biệt là trong các bản dựng gỡ lỗi. Và vì quan điểm của bố cục tiêu chuẩn là có khả năng tương thích với các ngôn ngữ khác, nên bạn không thể bố trí khác nhau trong gỡ lỗi so với phát hành.

Sau đó, có một thực tế là nó không thực sự làm tổn thương người dùng. Nếu bạn đang thực hiện một lớp được đóng gói, tỷ lệ cược là tốt cho tất cả các thành viên dữ liệu của bạn private. Bạn thường không để lộ các thành viên dữ liệu công khai về các loại được đóng gói đầy đủ. Vì vậy, đây sẽ chỉ là một vấn đề cho một vài người dùng muốn làm điều đó, những người muốn phân chia đó.

Vì vậy, nó không phải là mất mát lớn.

b) chỉ một lớp trong toàn bộ cây thừa kế có thể có các thành viên dữ liệu không tĩnh,

Lý do cho điều này trở lại lý do tại sao họ lại chuẩn hóa bố cục tiêu chuẩn: thông lệ.

Không thông lệ chung khi có hai thành viên của một cây thừa kế thực sự lưu trữ mọi thứ. Một số đặt lớp cơ sở trước lớp dẫn xuất, số khác làm theo cách khác. Cách nào để bạn ra lệnh cho các thành viên nếu họ đến từ hai lớp cơ sở? Và như thế. Trình biên dịch phân kỳ rất lớn về những câu hỏi này.

Ngoài ra, nhờ quy tắc zero / one / infinite, một khi bạn nói bạn có thể có hai lớp với các thành viên, bạn có thể nói bao nhiêu tùy ý. Điều này đòi hỏi phải thêm rất nhiều quy tắc bố trí cho cách xử lý này. Bạn phải nói làm thế nào nhiều kế thừa hoạt động, lớp nào đặt dữ liệu của họ trước các lớp khác, v.v ... Đó là rất nhiều quy tắc, cho rất ít lợi ích vật chất.

Bạn không thể tạo mọi thứ không có chức năng ảo và bố cục chuẩn của hàm tạo mặc định.

và thành viên dữ liệu không tĩnh đầu tiên không thể thuộc loại lớp cơ sở (điều này có thể phá vỡ các quy tắc răng cưa).

Tôi thực sự không thể nói chuyện này. Tôi không được giáo dục đủ về các quy tắc bí danh của C ++ để thực sự hiểu nó. Nhưng nó có liên quan đến thực tế là thành viên cơ sở sẽ chia sẻ cùng một địa chỉ với chính lớp cơ sở. Đó là:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

Và điều đó có thể chống lại các quy tắc răng cưa của C ++. Một cách nào đó.

Tuy nhiên, hãy xem xét điều này: làm thế nào hữu ích có thể có khả năng thực hiện điều này bao giờ thực sự ? Vì chỉ có một lớp có thể có các thành viên dữ liệu không tĩnh, Derivednên phải là lớp đó (vì nó có Basemột thành viên). Vì vậy, Base phải trống (của dữ liệu). Và nếu Basetrống, cũng như một lớp cơ sở ... tại sao lại có một thành viên dữ liệu của nó?

Baselà trống rỗng, nó không có nhà nước. Vì vậy, bất kỳ hàm thành viên không tĩnh nào sẽ làm những gì chúng làm dựa trên các tham số của chúng, không phải thiscon trỏ của chúng .

Vì vậy, một lần nữa: không mất mát lớn.


Cảm ơn đã giải thích, nó giúp rất nhiều. Có lẽ mặc dù static_cast<Base*>(&d)&d.bcùng Base*loại, họ chỉ ra những thứ khác nhau do đó phá vỡ quy tắc răng cưa. Xin hãy sửa cho tôi.
Andriy Tylychko

1
và, tại sao nếu chỉ có một lớp có thể có các thành viên dữ liệu không tĩnh, thì Derivedphải là lớp đó?
Andriy Tylychko

3
@AndyT: Để Derivedthành viên đầu tiên là lớp cơ sở của nó, nó phải có hai thứ: lớp cơ sở và thành viên . Và vì chỉ có một lớp trong hệ thống phân cấp có thể có các thành viên (và vẫn là bố cục tiêu chuẩn), điều này có nghĩa là lớp cơ sở của nó không thể có các thành viên.
Nicol Bolas

3
@AndyT, Yeah, về cơ bản bạn chính xác, IME, về quy tắc răng cưa. Hai trường hợp riêng biệt cùng loại được yêu cầu phải có địa chỉ bộ nhớ riêng biệt. (Điều này cho phép theo dõi nhận dạng đối tượng bằng địa chỉ bộ nhớ.) Đối tượng cơ sở và thành viên dẫn xuất đầu tiên là các trường hợp khác nhau, do đó chúng phải có các địa chỉ khác nhau, buộc phải thêm phần đệm, ảnh hưởng đến bố cục của lớp. Nếu chúng thuộc các loại khác nhau, nó sẽ không thành vấn đề; các đối tượng với các loại khác nhau được phép có cùng một địa chỉ (ví dụ một lớp và thành viên dữ liệu đầu tiên của nó).
Adam H. Peterson

46

Thay đổi trong C ++ 17

Tải về bản thảo cuối cùng của C ++ 17 International Standard tại đây .

Cốt liệu

C ++ 17 mở rộng và tăng cường tổng hợp và khởi tạo tổng hợp. Thư viện tiêu chuẩn hiện cũng bao gồm một std::is_aggregatelớp đặc điểm loại. Dưới đây là định nghĩa chính thức từ phần 11.6.1.1 và 11.6.1.2 (tham chiếu nội bộ đã bỏ qua):

Tập hợp là một mảng hoặc một lớp với
- không có các hàm tạo do người dùng cung cấp, rõ ràng hoặc được kế thừa,
- không có các thành viên dữ liệu không tĩnh riêng tư hoặc được bảo vệ,
- không có các hàm ảo và
- không có các lớp cơ sở ảo, riêng hoặc được bảo vệ.
[Lưu ý: Khởi tạo tổng hợp không cho phép truy cập các thành viên hoặc nhà xây dựng của lớp cơ sở được bảo vệ và riêng tư. Lưu ý
về phần tử ] Các phần tử của một tổng hợp là:
- đối với một mảng, các phần tử mảng theo thứ tự đăng ký tăng hoặc
- đối với một lớp, các lớp cơ sở trực tiếp theo thứ tự khai báo, theo sau là các thành viên dữ liệu không tĩnh trực tiếp không thành viên của một liên minh ẩn danh, theo thứ tự khai báo.

Những gì đã thay đổi?

  1. Các tập hợp hiện có thể có các lớp cơ sở công cộng, không ảo. Hơn nữa, nó không phải là một yêu cầu mà các lớp cơ sở được tổng hợp. Nếu chúng không tổng hợp, chúng được khởi tạo danh sách.
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Các hàm tạo mặc định rõ ràng không được phép
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Kế thừa các nhà xây dựng không được phép
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Lớp học tầm thường

Định nghĩa của lớp tầm thường đã được làm lại trong C ++ 17 để giải quyết một số khiếm khuyết không được giải quyết trong C ++ 14. Những thay đổi về bản chất là kỹ thuật. Dưới đây là định nghĩa mới tại 12.0.6 (tham chiếu nội bộ đã bỏ qua):

Một lớp có thể
sao chép tầm thường là một lớp: - trong đó mỗi hàm tạo sao chép, hàm tạo di chuyển, toán tử gán gán sao chép và toán tử gán chuyển động bị xóa hoặc tầm thường,
- có ít nhất một hàm tạo sao chép không xóa, hàm tạo di chuyển, toán tử gán sao chép, hoặc di chuyển toán tử gán, và
- có một hàm hủy không tầm thường, không bị xóa.
Một lớp tầm thường là một lớp có thể sao chép tầm thường và có một hoặc nhiều hàm tạo mặc định, tất cả đều là tầm thường hoặc bị xóa và ít nhất một trong số đó không bị xóa. [Lưu ý: Cụ thể, một lớp có thể sao chép hoặc tầm thường không có chức năng ảo hoặc các lớp cơ sở ảo.

Thay đổi:

  1. Trong C ++ 14, đối với một lớp là tầm thường, lớp không thể có bất kỳ toán tử sao chép / di chuyển xây dựng / gán nào là không tầm thường. Tuy nhiên, sau đó một ngầm tuyên bố như constructor defaulted / nhà điều hành có thể là không tầm thường và chưa được xác định như xóa vì, ví dụ, lớp chứa một subobject của kiểu lớp mà không thể được sao chép / di chuyển. Sự hiện diện của hàm tạo / toán tử không tầm thường, được xác định là đã xóa như vậy sẽ làm cho toàn bộ lớp không tầm thường. Một vấn đề tương tự đã tồn tại với các tàu khu trục. C ++ 17 làm rõ rằng sự hiện diện của hàm tạo / toán tử như vậy không làm cho lớp không thể sao chép một cách tầm thường, do đó không tầm thường và một lớp có thể sao chép tầm thường phải có hàm hủy không tầm thường, không bị xóa. DR1734 , DR1928
  2. C ++ 14 cho phép một lớp có thể sao chép tầm thường, do đó là một lớp tầm thường, có mọi toán tử sao chép / di chuyển xây dựng / toán tử gán được khai báo là đã xóa. Nếu như lớp cũng là bố cục tiêu chuẩn, tuy nhiên, nó có thể được sao chép / di chuyển hợp pháp với std::memcpy. Đây là một mâu thuẫn ngữ nghĩa, bởi vì, bằng cách định nghĩa là đã xóa tất cả các toán tử hàm tạo / gán, người tạo lớp dự định rõ ràng rằng lớp không thể được sao chép / di chuyển, nhưng lớp vẫn đáp ứng định nghĩa của lớp có thể sao chép tầm thường. Do đó, trong C ++ 17, chúng ta có một mệnh đề mới nói rằng lớp có thể sao chép tầm thường phải có ít nhất một toán tử sao chép / di chuyển sao chép / di chuyển không cần truy cập công khai). Xem N4148 , DR1734
  3. Thay đổi kỹ thuật thứ ba liên quan đến một vấn đề tương tự với các nhà xây dựng mặc định. Trong C ++ 14, một lớp có thể có các hàm tạo mặc định tầm thường được định nghĩa ngầm là đã xóa, nhưng vẫn là một lớp tầm thường. Định nghĩa mới làm rõ rằng một lớp tầm thường phải có ít nhất một hàm tạo mặc định không xóa, tầm thường. Xem DR1496

Các lớp bố trí tiêu chuẩn

Định nghĩa về bố cục tiêu chuẩn cũng được làm lại để giải quyết các báo cáo lỗi. Một lần nữa những thay đổi về bản chất là kỹ thuật. Đây là văn bản từ tiêu chuẩn (12.0.7). Như trước đây, các tài liệu tham khảo nội bộ được tách ra:

Một lớp S là một lớp bố cục tiêu chuẩn nếu nó:
- không có thành viên dữ liệu không tĩnh của loại lớp bố cục không chuẩn (hoặc mảng của các loại đó) hoặc tham chiếu,
- không có chức năng ảo và không có lớp cơ sở ảo,
- có cùng điều khiển truy cập cho tất cả các thành viên dữ liệu không tĩnh,
- không có các lớp cơ sở bố cục không chuẩn,
- có nhiều nhất một tiểu nhóm lớp cơ sở của bất kỳ loại đã cho nào,
- có tất cả các thành viên dữ liệu không tĩnh và các trường bit trong lớp và các lớp cơ sở của nó được khai báo đầu tiên trong cùng một lớp và
- không có phần tử nào của tập M (S) của các loại (được định nghĩa dưới đây) như là một lớp cơ sở.108
M (X) được định nghĩa như sau:
- Nếu X là loại lớp không liên kết không có thành viên dữ liệu không tĩnh (có thể được kế thừa), thì tập M (X) trống.
- Nếu X là loại lớp không liên kết có thành viên dữ liệu không tĩnh đầu tiên có loại X0 (trong đó thành viên có thể là liên kết ẩn danh), tập hợp M (X) bao gồm X0 và ​​các phần tử của M (X0).
- Nếu X là loại kết hợp, tập hợp M (X) là liên kết của tất cả M (Ui) và tập hợp chứa tất cả Ui, trong đó mỗi Ui là loại thành viên dữ liệu không tĩnh thứ i của X.
- Nếu X là kiểu mảng có kiểu phần tử Xe, tập M (X) bao gồm Xe và các phần tử của M (Xe).
- Nếu X là loại không có lớp, không có mảng, tập M (X) trống.
[Lưu ý: M (X) là tập hợp các loại của tất cả các tiểu dự án không thuộc lớp cơ sở được đảm bảo trong một lớp bố cục tiêu chuẩn ở mức bù bằng 0 trong X. Ghi chú]
[Ví dụ:

struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class
Ví dụ về mối quan hệ]
108) Điều này đảm bảo rằng hai tiểu dự án có cùng loại lớp và thuộc cùng một đối tượng dẫn xuất nhất không được phân bổ tại cùng một địa chỉ.

Thay đổi:

  1. Làm rõ rằng yêu cầu chỉ có một lớp trong cây phái sinh "có" các thành viên dữ liệu không tĩnh đề cập đến một lớp nơi các thành viên dữ liệu đó được khai báo trước, không phải là các lớp nơi chúng có thể được kế thừa và mở rộng yêu cầu này sang các trường bit không tĩnh . Cũng làm rõ rằng một lớp bố cục tiêu chuẩn "có nhiều nhất một tiểu nhóm lớp cơ sở của bất kỳ loại đã cho nào." Xem DR1813 , DR1881
  2. Định nghĩa về bố cục tiêu chuẩn chưa bao giờ cho phép loại của bất kỳ lớp cơ sở nào cùng loại với thành viên dữ liệu không tĩnh đầu tiên. Đó là để tránh tình huống một thành viên dữ liệu ở offset 0 có cùng loại với bất kỳ lớp cơ sở nào. Tiêu chuẩn C ++ 17 cung cấp một định nghĩa đệ quy chặt chẽ hơn về "tập hợp các loại của tất cả các tiểu dự án không thuộc lớp cơ sở được đảm bảo trong một lớp bố cục tiêu chuẩn ở mức bù 0" để cấm các loại đó từ loại của bất kỳ lớp cơ sở. Xem DR1672 , DR2120 .

Lưu ý: Ủy ban tiêu chuẩn C ++ dự định các thay đổi trên dựa trên các báo cáo lỗi để áp dụng cho C ++ 14, mặc dù ngôn ngữ mới không nằm trong tiêu chuẩn C ++ 14 được công bố. Đó là trong tiêu chuẩn C ++ 17.


Lưu ý Tôi vừa cập nhật câu trả lời của mình, lỗi thay đổi bố cục tiêu chuẩn có trạng thái CD4 có nghĩa là chúng thực sự được áp dụng cho C ++ 14. Đó là lý do tại sao câu trả lời của tôi không bao gồm chúng b / c điều này xảy ra sau khi tôi viết câu trả lời của mình.
Shafik Yaghmour

Lưu ý, tôi đã bắt đầu một tiền thưởng cho câu hỏi này.
Shafik Yaghmour

Cảm ơn @ShafikYaghmour. Tôi sẽ xem xét tình trạng báo cáo lỗi và sửa đổi câu trả lời của tôi cho phù hợp.
ThomasMcLeod

@ShafikYaghmour, Sau khi xem xét quá trình C ++ 14 và tôi thấy rằng, trong khi những DR này được "chấp nhận" tại cuộc họp Rapperswil tháng 6 năm 2014, cuộc họp Issaquah tháng 2 năm 2014 đã trở thành C ++ 14. Xem isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "theo quy tắc ISO, chúng tôi đã không chính thức phê duyệt bất kỳ chỉnh sửa nào đối với tài liệu làm việc của C ++." Tui bỏ lỡ điều gì vậy?
ThomasMcLeod

Họ có trạng thái 'CD4, có nghĩa là họ nên áp dụng trong chế độ C ++ 14.
Shafik Yaghmour

14

Có gì thay đổi trong

Tiếp theo phần còn lại của chủ đề rõ ràng của câu hỏi này, ý nghĩa và cách sử dụng tổng hợp tiếp tục thay đổi theo mọi tiêu chuẩn. Có một số thay đổi quan trọng trên đường chân trời.

Các loại với hàm tạo do người dùng khai báo P1008

Trong C ++ 17, loại này vẫn là tổng hợp:

struct X {
    X() = delete;
};

Và do đó, X{}vẫn biên dịch bởi vì đó là khởi tạo tổng hợp - không phải là một lời gọi hàm tạo. Xem thêm: Khi nào thì một constructor riêng không phải là constructor riêng?

Trong C ++ 20, hạn chế sẽ thay đổi so với yêu cầu:

không có nhà explicitxây dựng do người dùng cung cấp hoặc kế thừa

đến

không có nhà xây dựng do người dùng khai báo hoặc kế thừa

Điều này đã được áp dụng vào dự thảo làm việc C ++ 20 . Cả câu hỏi Xở đây lẫn Ccâu hỏi được liên kết sẽ không được tổng hợp trong C ++ 20.

Điều này cũng tạo ra hiệu ứng yo-yo với ví dụ sau:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

Trong C ++ 11/14, Bđã không một tổng hợp do các lớp cơ sở, vì vậy B{}Thực hiện giá trị khởi tạo mà các cuộc gọi B::B()mà các cuộc gọi A::A(), tại một điểm mà nó có thể truy cập. Điều này đã được hình thành tốt.

Trong C ++ 17, Bđã trở thành một tổng hợp vì các lớp cơ sở được cho phép, điều này tạo ra B{}khởi tạo tổng hợp. Điều này đòi hỏi phải sao chép danh sách khởi tạo Atừ {}, nhưng từ bên ngoài bối cảnh B, nơi không thể truy cập được. Trong C ++ 17, điều này không đúng định dạng ( auto x = B();mặc dù sẽ ổn).

Bây giờ, trong C ++ 20, do thay đổi quy tắc ở trên, Bmột lần nữa không còn là tổng hợp (không phải vì lớp cơ sở, mà là do hàm tạo mặc định do người dùng khai báo - mặc dù nó được mặc định). Vì vậy, chúng tôi quay trở lại để thông qua nhà Bxây dựng và đoạn trích này được hình thành tốt.

Khởi tạo tập hợp từ danh sách giá trị được ngoặc đơn P960

Một vấn đề phổ biến xuất hiện là muốn sử dụng các hàm tạo emplace()kiểu với các tổng hợp:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

Điều này không hoạt động, bởi vì emplacesẽ cố gắng thực hiện hiệu quả việc khởi tạo X(1, 2), không hợp lệ. Giải pháp điển hình là thêm một hàm tạo X, nhưng với đề xuất này (hiện đang thực hiện thông qua Core), các tập hợp sẽ có hiệu quả tổng hợp các hàm tạo thực hiện đúng - và hoạt động như các hàm tạo thông thường. Đoạn mã trên sẽ biên dịch nguyên trạng trong C ++ 20.

Khấu trừ đối số mẫu lớp ( CTAD ) cho các khối tổng hợp P1021 (cụ thể là P1816 )

Trong C ++ 17, điều này không biên dịch:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Người dùng sẽ phải viết hướng dẫn khấu trừ của riêng họ cho tất cả các mẫu tổng hợp:

template <typename T> Point(T, T) -> Point<T>;

Nhưng vì điều này theo một nghĩa nào đó là "điều hiển nhiên" phải làm, và về cơ bản chỉ là bản tóm tắt, ngôn ngữ sẽ làm điều này cho bạn. Ví dụ này sẽ biên dịch trong C ++ 20 (không cần hướng dẫn khấu trừ do người dùng cung cấp).


Mặc dù tôi sẽ nâng cấp, nhưng tôi cảm thấy hơi sớm khi thêm phần này, tôi không biết bất cứ điều gì quan trọng sẽ thay đổi điều này trước khi C ++ 2x được thực hiện.
Shafik Yaghmour

@ShafikYaghmour Vâng, có lẽ CÁCH quá sớm. Nhưng cho rằng SD là hạn chót cho các tính năng ngôn ngữ mới, đây có phải là hai chuyến bay duy nhất mà tôi biết - trường hợp xấu nhất tôi chỉ chặn một trong những phần này sau? Tôi chỉ thấy câu hỏi hoạt động với tiền thưởng và nghĩ rằng đây là thời điểm tốt để hòa nhập trước khi tôi quên.
Barry

Tôi hiểu, tôi đã bị cám dỗ một vài lần cho các trường hợp tương tự. Tôi luôn lo lắng một cái gì đó lớn sẽ thay đổi và cuối cùng tôi sẽ phải viết lại nó.
Shafik Yaghmour 17/12/18

@ShafikYaghmour Có vẻ như sẽ không có gì thay đổi ở đây :)
Barry

Tôi hy vọng điều này được cập nhật ngay bây giờ, với C ++ 20 đã được phát hành
Noone At ALL
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.