Apa itu Agregat dan POD dan bagaimana / mengapa mereka istimewa?

548

Ini FAQ adalah tentang Agregat dan PODS dan mencakup materi berikut:

  • Apa itu Agregat ?
  • Apa itu POD (Data Lama Biasa)?
  • Bagaimana mereka terkait?
  • Bagaimana dan mengapa mereka istimewa?
  • Apa perubahan untuk C ++ 11?
Armen Tsirunyan
sumber
1
Apa itu subset POD: stackoverflow.com/questions/146452/what-are-pod-types-in-c
Ciro Santilli 郝海东 冠状 病 六四 事件 事件 法轮功
Dapatkah dikatakan bahwa motivasi di balik definisi ini kira-kira: POD == memcpy'able, Agregat == agregat-dapat diinisialisasi?
Ofek Shilon

Jawaban:

572

Cara membaca:

Artikel ini agak panjang. Jika Anda ingin tahu tentang agregat dan POD (Data Lama Biasa) luangkan waktu dan bacalah. Jika Anda tertarik hanya pada agregat, baca hanya bagian pertama. Jika Anda hanya tertarik pada POD maka Anda harus terlebih dahulu membaca definisi, implikasi, dan contoh agregat dan kemudian Anda dapat melompat ke POD tetapi saya masih akan merekomendasikan membaca bagian pertama secara keseluruhan. Gagasan agregat sangat penting untuk mendefinisikan POD. Jika Anda menemukan kesalahan (bahkan minor, termasuk tata bahasa, gaya bahasa, pemformatan, sintaksis, dll.) Silakan tinggalkan komentar, saya akan mengedit.

Jawaban ini berlaku untuk C ++ 03. Untuk standar C ++ lainnya, lihat:

Apa itu agregat dan mengapa mereka istimewa

Definisi formal dari standar C ++ ( C ++ 03 8.5.1 §1 ) :

Agregat adalah array atau kelas (klausa 9) tanpa konstruktor yang dideklarasikan pengguna (12.1), tidak ada anggota data non-statis pribadi atau yang dilindungi (klausa 11), tidak ada kelas dasar (klausa 10), dan tidak ada fungsi virtual (10.3 ).

Jadi, OK, mari kita uraikan definisi ini. Pertama-tama, array apa pun adalah agregat. Kelas juga bisa menjadi agregat jika ... tunggu! tidak ada yang dikatakan tentang struct atau serikat pekerja, tidak bisakah mereka agregat? Ya mereka bisa. Dalam C ++, istilah ini classmerujuk ke semua kelas, struct, dan serikat pekerja. Jadi, sebuah kelas (atau struct, atau union) adalah agregat jika dan hanya jika memenuhi kriteria dari definisi di atas. Apa yang tersirat dari kriteria ini?

  • Ini tidak berarti kelas agregat tidak dapat memiliki konstruktor, pada kenyataannya ia dapat memiliki konstruktor default dan / atau copy konstruktor selama mereka secara implisit dinyatakan oleh kompiler, dan tidak secara eksplisit oleh pengguna

  • Tidak ada anggota data non-statis pribadi atau yang dilindungi . Anda dapat memiliki banyak fungsi anggota pribadi dan terlindungi (tetapi bukan konstruktor) serta sebanyak mungkin fungsi data anggota dan statis anggota dan fungsi anggota sebanyak yang Anda suka dan tidak melanggar aturan untuk kelas agregat

  • Kelas agregat dapat memiliki operator penugasan dan / atau destruktor yang dideklarasikan oleh pengguna dan / atau yang ditentukan pengguna

  • Array adalah agregat bahkan jika itu adalah array tipe kelas non-agregat.

Sekarang mari kita lihat beberapa contoh:

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

Anda mendapatkan idenya. Sekarang mari kita lihat bagaimana agregat itu istimewa. Mereka, tidak seperti kelas non-agregat, dapat diinisialisasi dengan kurung kurawal {}. Sintaks inisialisasi ini umumnya dikenal untuk array, dan kami baru mengetahui bahwa ini adalah agregat. Jadi, mari kita mulai dengan mereka.

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

if (m == n)
i th elemen dari array diinisialisasi dengan i
lain jika (m <n)
elemen m pertama dari array diinisialisasi dengan 1 , sebuah 2 , ..., a m dan lainnyan - melemen adalah, jika mungkin, nilai diinisialisasi (lihat di bawah untuk penjelasan istilah)
jika tidak (m> n)
kompiler akan mengeluarkan kesalahan
lain (ini adalah kasus ketika n tidak ditentukan sama sekali seperti int a[] = {1, 2, 3};)
ukuran array (n) diasumsikan sama dengan m, jadiint a[] = {1, 2, 3};sama denganint a[3] = {1, 2, 3};

Ketika sebuah objek dari tipe skalar ( bool, int, char, double, pointer, dll) adalah nilai-diinisialisasi itu berarti itu diinisialisasi dengan 0untuk jenis yang ( falseuntuk bool, 0.0untuk double, dll). Ketika objek tipe kelas dengan konstruktor default yang dideklarasikan pengguna diinisialisasi nilai konstruktor defaultnya disebut. Jika konstruktor default didefinisikan secara implisit, maka semua anggota nonstatis diinisialisasi nilai secara rekursif. Definisi ini tidak tepat dan sedikit salah tetapi seharusnya memberi Anda ide dasar. Referensi tidak dapat diinisialisasi nilai. Inisialisasi nilai untuk kelas non-agregat dapat gagal jika, misalnya, kelas tidak memiliki konstruktor default yang sesuai.

Contoh inisialisasi array:

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
}

Sekarang mari kita lihat bagaimana kelas agregat dapat diinisialisasi dengan kawat gigi. Hampir sama caranya. Alih-alih elemen array kita akan menginisialisasi anggota data non-statis dalam urutan penampilan mereka di definisi kelas (mereka semua publik menurut definisi). Jika ada lebih sedikit inisialisasi daripada anggota, sisanya diinisialisasi-nilai. Jika tidak mungkin untuk menginisialisasi nilai salah satu anggota yang tidak diinisialisasi secara eksplisit, kami mendapatkan kesalahan waktu kompilasi. Jika ada lebih banyak inisialisasi dari yang diperlukan, kami juga mendapatkan kesalahan waktu kompilasi.

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

Dalam contoh di atas y.cdiinisialisasi dengan 'a', y.x.i1dengan 10, y.x.i2dengan 20, y.i[0]dengan 20, y.i[1]dengan 30dan y.fdiinisialisasi-nilai, yaitu diinisialisasi dengan 0.0. Anggota statis yang dilindungi dtidak diinisialisasi sama sekali, karena memang demikian static.

Agregat serikat berbeda karena Anda dapat menginisialisasi hanya anggota pertama mereka dengan kawat gigi. Saya pikir jika Anda cukup mahir dalam C ++ untuk bahkan mempertimbangkan menggunakan serikat pekerja (penggunaannya mungkin sangat berbahaya dan harus dipikirkan dengan hati-hati), Anda bisa mencari aturan serikat pekerja dalam standar sendiri :).

Sekarang kita tahu apa yang istimewa tentang agregat, mari kita coba memahami batasan pada kelas; itulah sebabnya mereka ada di sana. Kita harus memahami bahwa inisialisasi berdasarkan anggota dengan kawat gigi menyiratkan bahwa kelas tidak lebih dari jumlah anggotanya. Jika ada konstruktor yang ditentukan pengguna, itu berarti bahwa pengguna perlu melakukan beberapa pekerjaan tambahan untuk menginisialisasi anggota sehingga inisialisasi brace akan salah. Jika ada fungsi virtual, itu berarti bahwa objek dari kelas ini memiliki (pada sebagian besar implementasi) pointer ke apa yang disebut vtable kelas, yang diatur dalam konstruktor, sehingga inisialisasi brace tidak akan cukup. Anda bisa mengetahui batasan lainnya dengan cara yang sama seperti latihan :).

Cukup tentang agregat. Sekarang kita dapat mendefinisikan serangkaian tipe yang lebih ketat, yaitu, POD

Apa itu POD dan mengapa mereka istimewa

Definisi formal dari standar C ++ ( C ++ 03 9 §4 ) :

POD-struct adalah kelas agregat yang tidak memiliki anggota data non-statis dari tipe non-POD-struct, non-POD-union (atau array jenis seperti itu) atau referensi, dan tidak memiliki operator penugasan salinan yang ditentukan pengguna dan tidak ada destructor yang ditentukan pengguna. Demikian pula, serikat POD adalah serikat agregat yang tidak memiliki anggota data non-statis dari tipe non-POD-struct, non-POD-union (atau array jenis seperti itu) atau referensi, dan tidak memiliki operator penugasan salinan yang ditentukan pengguna dan tidak ada destruktor yang ditentukan pengguna. Kelas POD adalah kelas yang merupakan POD-struct atau POD-union.

Wow, yang ini lebih sulit untuk diurai, bukan? :) Mari kita tinggalkan serikat pekerja (dengan alasan yang sama seperti di atas) dan ulangi dengan cara yang sedikit lebih jelas:

Kelas agregat disebut POD jika tidak memiliki operator penugasan dan penghancur salinan yang ditentukan pengguna dan tidak ada anggota non-statisnya yang merupakan kelas non-POD, array non-POD, atau referensi.

Apa arti definisi ini? (Apakah saya menyebutkan POD singkatan dari Plain Old Data ?)

  • Semua kelas POD adalah agregat, atau, dengan kata lain, jika suatu kelas bukan agregat maka pasti bukan POD
  • Kelas, seperti halnya struct, dapat menjadi PODs meskipun istilah standarnya adalah POD-struct untuk kedua kasus
  • Sama seperti dalam kasus agregat, tidak masalah apa anggota statis kelas miliki

Contoh:

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

Kelas POD, serikat POD, tipe skalar, dan susunan tipe seperti itu secara kolektif disebut tipe POD.
POD spesial dalam banyak hal. Saya akan memberikan beberapa contoh saja.

  • Kelas POD adalah yang terdekat dengan C struct. Tidak seperti mereka, POD dapat memiliki fungsi anggota dan anggota statis sewenang-wenang, tetapi keduanya tidak mengubah tata letak memori objek. Jadi, jika Anda ingin menulis pustaka dinamis portabel yang kurang lebih dapat digunakan dari C dan bahkan. NET, Anda harus mencoba membuat semua fungsi yang diekspor mengambil dan hanya mengembalikan parameter tipe POD.

  • Masa objek objek non-POD kelas dimulai ketika konstruktor telah selesai dan berakhir ketika destruktor telah selesai. Untuk kelas POD, masa dimulai ketika penyimpanan untuk objek ditempati dan selesai ketika penyimpanan dilepaskan atau digunakan kembali.

  • Untuk objek tipe POD dijamin oleh standar bahwa ketika Anda memcpyisi objek Anda menjadi array char atau char unsigned, dan kemudian memcpykonten kembali ke objek Anda, objek akan memegang nilai aslinya. Perhatikan bahwa tidak ada jaminan untuk objek jenis non-POD. Anda juga dapat dengan aman menyalin objek POD memcpy. Contoh berikut menganggap T adalah tipe 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
  • pernyataan kebohongan. Seperti yang Anda ketahui, itu ilegal (kompiler harus mengeluarkan kesalahan) untuk melakukan lompatan melalui titik dari mana beberapa variabel belum dalam ruang lingkup ke titik di mana ia sudah dalam ruang lingkup. Pembatasan ini hanya berlaku jika variabelnya bukan tipe POD. Pada contoh berikut f()ini terbentuk buruk sedangkan terbentuk dengan g()baik. Perhatikan bahwa kompiler Microsoft terlalu liberal dengan aturan ini — ia hanya mengeluarkan peringatan dalam kedua kasus.

    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;
    }
  • Dijamin bahwa tidak akan ada padding di awal objek POD. Dengan kata lain, jika anggota pertama POD-kelas A adalah tipe T, Anda dapat dengan aman reinterpret_castdari A*ke T*dan mendapatkan pointer ke anggota pertama dan sebaliknya.

Daftar ini terus dan terus ...

Kesimpulan

Penting untuk memahami apa sebenarnya POD itu karena banyak fitur bahasa, seperti yang Anda lihat, berperilaku berbeda untuk mereka.

Armen Tsirunyan
sumber
3
Jawaban bagus. Komentar: "Jika konstruktor default didefinisikan secara implisit maka semua anggota non-statis secara inisial nilai diinisialisasi." dan "Inisialisasi nilai untuk kelas non-agregat dapat gagal jika, misalnya, kelas tidak memiliki konstruktor default yang sesuai." tidak benar: Nilai inisialisasi kelas dengan konstruktor default yang dinyatakan secara implisit tidak mengharuskan konstruktor default yang ditetapkan secara implisit. Jadi, diberikan (masukkan yang private:sesuai): struct A { int const a; };maka A()terbentuk dengan baik, bahkan jika Adefinisi konstruktor default akan menjadi buruk.
Johannes Schaub - litb
4
@Kev: Jika Anda berhasil mengemas informasi yang sama menjadi jawaban yang lebih pendek, kami semua akan dengan senang hati memilihnya!
sbi
3
@Armen juga mencatat Anda dapat melakukan beberapa jawaban untuk pertanyaan yang sama. Setiap jawaban dapat berisi bagian dari solusi untuk pertanyaan itu. Persetan dengan hal-hal yang diterima itu, menurut pendapat saya :)
Johannes Schaub - litb
3
Jawabannya bagus. Saya masih mengunjungi kembali posting ini beberapa kali. Omong-omong tentang peringatan untuk Visual Studio. "pernyataan goto" untuk pod datang dengan ketidaktahuan kepada kompiler MSVC seperti yang telah Anda sebutkan. Tetapi untuk switch / pernyataan kasus itu menghasilkan kesalahan kompilasi. Berdasarkan konsep itu, saya telah membuat beberapa test-pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz
2
Pada poin-poin yang dimulai dengan "Masa objek dari tipe kelas non-POD dimulai ketika konstruktor telah selesai dan berakhir ketika destruktor telah selesai." bagian terakhir seharusnya mengatakan "ketika destruktor dimulai."
Quokka
457

Apa perubahan untuk C ++ 11?

Agregat

Definisi standar suatu agregat telah sedikit berubah, tetapi masih hampir sama:

Agregat adalah larik atau kelas (Pasal 9) tanpa konstruktor yang disediakan pengguna (12.1), tanpa inisialisasi brace-atau-sama untuk anggota data non-statis (9.2), tidak ada anggota data non-statis pribadi atau yang dilindungi ( Klausa 11), tidak ada kelas dasar (Klausa 10), dan tidak ada fungsi virtual (10.3).

Ok, apa yang berubah?

  1. Sebelumnya, agregat tidak boleh memiliki konstruktor yang dideklarasikan pengguna , tetapi sekarang agregat tidak dapat memiliki konstruktor yang disediakan pengguna . Apakah ada perbedaan? Ya, ada, karena sekarang Anda dapat mendeklarasikan konstruktor dan membuat default :

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

    Ini masih agregat karena konstruktor (atau fungsi anggota khusus apa pun) yang default pada deklarasi pertama tidak disediakan pengguna.

  2. Sekarang agregat tidak dapat memiliki inisialisasi brace-atau-sama untuk anggota data non-statis. Apa artinya ini? Nah, ini hanya karena dengan standar baru ini, kita dapat menginisialisasi anggota secara langsung di kelas seperti ini:

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

    Menggunakan fitur ini membuat kelas tidak lagi agregat karena pada dasarnya setara dengan menyediakan konstruktor default Anda sendiri.

Jadi, apa itu agregat tidak banyak berubah sama sekali. Itu masih ide dasar yang sama, disesuaikan dengan fitur-fitur baru.

Bagaimana dengan POD?

POD mengalami banyak perubahan. Banyak aturan sebelumnya tentang POD dilonggarkan dalam standar baru ini, dan cara definisi diberikan dalam standar diubah secara radikal.

Ide POD adalah untuk menangkap dasarnya dua sifat yang berbeda:

  1. Ini mendukung inisialisasi statis, dan
  2. Mengkompilasi POD dalam C ++ memberi Anda tata letak memori yang sama dengan struct yang dikompilasi dalam C.

Karena itu, definisi telah dibagi menjadi dua konsep yang berbeda: kelas sepele dan kelas tata letak standar , karena ini lebih berguna daripada POD. Standar sekarang jarang menggunakan istilah POD, lebih suka konsep - konsep sepele dan tata letak standar yang lebih spesifik .

Definisi baru pada dasarnya mengatakan bahwa POD adalah kelas yang sepele dan memiliki tata letak standar, dan properti ini harus tahan secara rekursif untuk semua anggota data non-statis:

POD struct adalah kelas non-serikat yang merupakan kelas sepele dan kelas tata letak standar, dan tidak memiliki anggota data non-statis dari tipe non-POD struct, non-POD union (atau array jenis seperti itu). Demikian pula, serikat POD adalah serikat yang merupakan kelas trivial dan kelas tata letak standar, dan tidak memiliki anggota data non-statis dari tipe non-POD struct, non-POD union (atau array jenis seperti itu). Kelas POD adalah kelas yang merupakan struct POD atau serikat POD.

Mari kita membahas masing-masing dari dua properti ini secara terpisah.

Kelas sepele

Trivial adalah properti pertama yang disebutkan di atas: kelas-kelas sepele mendukung inisialisasi statis. Jika suatu kelas dapat ditiru secara sepele (superset dari kelas-kelas sepele), memcpyboleh saja menyalin perwakilannya atas tempat tersebut dengan hal-hal seperti dan mengharapkan hasilnya sama.

Standar mendefinisikan kelas sepele sebagai berikut:

Kelas yang dapat disalin sepele adalah kelas yang:

- tidak memiliki konstruktor salinan non-sepele (12.8),

- tidak memiliki konstruktor bergerak non-sepele (12.8),

- tidak memiliki operator penugasan salinan non-sepele (13.5.3, 12.8),

- tidak memiliki operator penugasan non-sepele (13.5.3, 12.8), dan

- Memiliki destruktor sepele (12,4).

Kelas trivial adalah kelas yang memiliki konstruktor standar sepele (12.1) dan dapat disalin secara sepele.

[ Catatan: Secara khusus, kelas yang dapat disalin atau sepele secara sepele tidak memiliki fungsi virtual atau kelas dasar virtual. —Kirim catatan ]

Jadi, apa saja hal-hal sepele dan non-sepele itu?

Copy / move constructor untuk kelas X adalah sepele jika tidak disediakan pengguna dan jika

- kelas X tidak memiliki fungsi virtual (10.3) dan tidak ada kelas basis virtual (10.1), dan

- konstruktor yang dipilih untuk menyalin / memindahkan setiap sub kelas objek langsung adalah sepele, dan

- untuk setiap anggota data non-statis X yang bertipe kelas (atau lariknya), konstruktor yang dipilih untuk menyalin / memindahkan anggota itu sepele;

jika tidak, copy / move constructor adalah non-sepele.

Pada dasarnya ini berarti bahwa salinan atau memindahkan konstruktor sepele jika tidak disediakan pengguna, kelas tidak memiliki virtual di dalamnya, dan properti ini berlaku secara rekursif untuk semua anggota kelas dan untuk kelas dasar.

Definisi operator penugasan / pemindahan sepele yang sangat mirip, hanya mengganti kata "konstruktor" dengan "operator penugasan".

Destroyer sepele juga memiliki definisi yang mirip, dengan batasan tambahan bahwa itu tidak bisa virtual.

Dan aturan lain yang serupa ada untuk konstruktor default sepele, dengan tambahan bahwa konstruktor default tidak sepele jika kelas memiliki anggota data non-statis dengan inisialisasi brace-atau-sama , yang telah kita lihat di atas.

Berikut adalah beberapa contoh untuk membersihkan semuanya:

// 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
};

Tata letak standar

Tata letak standar adalah properti kedua. Standar menyebutkan bahwa ini berguna untuk berkomunikasi dengan bahasa lain, dan itu karena kelas tata letak standar memiliki tata letak memori yang sama dengan struct atau union C yang setara.

Ini adalah properti lain yang harus dipegang secara rekursif untuk anggota dan semua kelas dasar. Dan seperti biasa, tidak ada fungsi virtual atau kelas dasar virtual yang diizinkan. Itu akan membuat tata letak tidak kompatibel dengan C.

Aturan yang santai di sini adalah bahwa kelas tata letak standar harus memiliki semua anggota data non-statis dengan kontrol akses yang sama. Sebelumnya ini harus semua publik , tetapi sekarang Anda dapat menjadikannya pribadi atau dilindungi, asalkan semuanya pribadi atau semua dilindungi.

Saat menggunakan pewarisan, hanya satu kelas di seluruh pohon warisan yang dapat memiliki anggota data non-statis, dan anggota data non-statis pertama tidak boleh dari tipe kelas dasar (ini dapat melanggar aturan alias), jika tidak, itu bukan standar- kelas tata letak.

Ini adalah bagaimana definisi berjalan dalam teks standar:

Kelas tata letak standar adalah kelas yang:

- tidak memiliki anggota data non-statis dari kelas tipe non-standar-tata letak (atau array jenis seperti itu) atau referensi,

- tidak memiliki fungsi virtual (10.3) dan tidak ada kelas basis virtual (10.1),

- memiliki kontrol akses yang sama (Pasal 11) untuk semua anggota data non-statis,

- tidak memiliki kelas dasar tata letak non-standar,

- Entah tidak memiliki anggota data non-statis di kelas yang paling diturunkan dan paling banyak satu kelas dasar dengan anggota data non-statis, atau tidak memiliki kelas basis dengan anggota data non-statis, dan

- tidak memiliki kelas dasar dengan tipe yang sama dengan anggota data non-statis pertama.

Struct tata letak standar adalah kelas tata letak standar yang didefinisikan dengan struct kunci kelas atau kelas kunci kunci.

Serikat tata letak standar adalah kelas tata letak standar yang ditentukan dengan ikatan kunci kelas.

[ Catatan: Kelas tata letak standar berguna untuk berkomunikasi dengan kode yang ditulis dalam bahasa pemrograman lain. Tata letak mereka ditentukan dalam 9.2. —Kirim catatan ]

Dan mari kita lihat beberapa contoh.

// 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

Kesimpulan

Dengan aturan baru ini, lebih banyak tipe yang bisa menjadi POD sekarang. Dan bahkan jika suatu tipe bukan POD, kita dapat mengambil keuntungan dari beberapa properti POD secara terpisah (jika itu hanya salah satu dari sepele atau tata letak standar).

Pustaka standar memiliki sifat untuk menguji properti ini di header <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;
R. Martinho Fernandes
sumber
2
bisa tolong jelaskan aturan berikut: a) kelas tata letak standar harus memiliki semua anggota data non-statis dengan kontrol akses yang sama; b) hanya satu kelas di seluruh pohon warisan yang dapat memiliki anggota data non-statis, dan anggota data non-statis pertama tidak boleh dari tipe kelas dasar (ini dapat melanggar aturan alias). Terutama apa alasan mereka? Untuk aturan selanjutnya, dapatkah Anda memberikan contoh melanggar alias?
Andriy Tylychko
@AndyT: Lihat jawaban saya. Saya mencoba menjawab yang terbaik dari pengetahuan saya.
Nicol Bolas
5
Mungkin ingin memperbarui ini untuk C ++ 14, yang menghapus persyaratan "no brace-or-equal-initializers" untuk agregat.
TC
@TC terima kasih atas bantuannya. Saya akan mencari perubahan itu segera dan memperbaruinya.
R. Martinho Fernandes
1
Mengenai aliasing: Ada aturan tata letak C ++ bahwa jika kelas C memiliki basis (kosong) X, dan anggota data pertama dari C adalah tipe X, maka anggota pertama itu tidak bisa berada pada offset yang sama dengan basis X; itu mendapat byte padding dummy di depannya, jika diperlukan untuk menghindari itu. Memiliki dua instance X (atau subclass) di alamat yang sama dapat memecahkan hal-hal yang perlu membedakan instance yang berbeda melalui alamatnya (instance kosong tidak memiliki hal lain untuk membedakannya ...). Bagaimanapun juga, kebutuhan untuk memasukkan byte padding memecah 'tata letak yang kompatibel'.
greggo
106

Apa yang berubah untuk C ++ 14

Kita dapat merujuk ke standar Draft C ++ 14 untuk referensi.

Agregat

Ini dicakup dalam bagian 8.5.1 Agregat yang memberi kita definisi berikut:

Agregat adalah array atau kelas (Klausul 9) tanpa konstruktor yang disediakan pengguna (12.1), tidak ada anggota data non-statis pribadi atau yang dilindungi (Klausul 11), tidak ada kelas dasar (Klausul 10), dan tidak ada fungsi virtual (10.3 ).

Satu-satunya perubahan sekarang adalah menambahkan inisialisasi anggota di dalam kelas tidak membuat sebuah kelas menjadi non-agregat. Jadi contoh berikut dari inisialisasi agregat C ++ 11 untuk kelas dengan inisialisasi in-pace anggota :

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

bukan agregat dalam C ++ 11 tetapi dalam C ++ 14. Perubahan ini tercakup dalam N3605: Inisialisasi dan agregat anggota , yang memiliki abstrak berikut:

Bjarne Stroustrup dan Richard Smith mengemukakan masalah tentang inisialisasi agregat dan inisialisasi anggota yang tidak bekerja bersama. Makalah ini mengusulkan untuk memperbaiki masalah dengan mengadopsi kata-kata yang diusulkan Smith yang menghilangkan batasan yang tidak dapat dimiliki oleh agregat.

POD tetap sama

Definisi untuk struct POD ( data lama biasa ) dicakup dalam bagian 9 Kelas yang mengatakan:

POD struct 110 adalah kelas non-serikat yang merupakan kelas trivial dan kelas tata letak standar, dan tidak memiliki anggota data non-statis dari tipe non-POD struct, non-POD union (atau array jenis seperti itu). Demikian pula, serikat POD adalah serikat yang merupakan kelas trivial dan kelas tata letak standar, dan tidak memiliki anggota data non-statis dari tipe non-POD struct, non-POD union (atau array jenis seperti itu). Kelas POD adalah kelas yang merupakan struct POD atau serikat POD.

yang merupakan kata-kata yang sama dengan C ++ 11.

Perubahan Tata Letak Standar untuk C ++ 14

Seperti disebutkan dalam komentar pod bergantung pada definisi tata letak standar dan yang memang berubah untuk C ++ 14 tetapi ini melalui laporan cacat yang diterapkan ke C ++ 14 setelah fakta.

Ada tiga DR:

Jadi tata letak standar beralih dari Pre C ++ 14 ini:

Kelas tata letak standar adalah kelas yang:

  • (7.1) tidak memiliki anggota data non-statis dari kelas tipe non-standar-tata letak (atau larik jenis tersebut) atau referensi,
  • (7.2) tidak memiliki fungsi virtual ([class.virtual]) dan tidak ada kelas basis virtual ([class.mi]),
  • (7.3) memiliki kontrol akses yang sama (Klausa [class.access]) untuk semua anggota data non-statis,
  • (7.4) tidak memiliki kelas dasar tata letak non-standar,
  • (7.5) tidak memiliki anggota data non-statis di kelas yang paling diturunkan dan paling banyak satu kelas dasar dengan anggota data non-statis, atau tidak memiliki kelas basis dengan anggota data non-statis, dan
  • (7.6) tidak memiliki kelas dasar dengan tipe yang sama dengan anggota data non-statis pertama.109

Untuk ini dalam C ++ 14 :

Kelas S adalah kelas tata letak standar jika:

  • (3.1) tidak memiliki anggota data non-statis dari kelas tipe non-standar-tata letak (atau array jenis tersebut) atau referensi,
  • (3.2) tidak memiliki fungsi virtual dan tidak ada kelas dasar virtual,
  • (3.3) memiliki kontrol akses yang sama untuk semua anggota data non-statis,
  • (3.4) tidak memiliki kelas dasar non-standar-tata letak,
  • (3.5) memiliki paling banyak satu sub-proyek kelas dasar dari jenis apa pun,
  • (3.6) memiliki semua anggota data non-statis dan bit-bidang di kelas dan kelas dasarnya dideklarasikan di kelas yang sama, dan
  • (3.7) tidak memiliki elemen himpunan M (S) jenis sebagai kelas dasar, di mana untuk semua tipe X, M (X) didefinisikan sebagai berikut.104 [Catatan: M (X) adalah himpunan jenis semua sub-objek non-basis-kelas yang mungkin berada pada titik nol di X. - catatan akhir]
    • (3.7.1) Jika X adalah tipe kelas non-serikat tanpa anggota data yang tidak (mungkin diwariskan), set M (X) kosong.
    • (3.7.2) Jika X adalah tipe kelas non-union dengan anggota data non-statis tipe X0 yang berukuran nol atau merupakan anggota data non-statis pertama X (di mana anggota tersebut dapat merupakan serikat anonim ), himpunan M (X) terdiri dari X0 dan unsur-unsur M (X0).
    • (3.7.3) Jika X adalah tipe gabungan, himpunan M (X) adalah gabungan dari semua M (Ui) dan himpunan yang berisi semua Ui, di mana setiap Ui adalah jenis anggota data non-statis engan X .
    • (3.7.4) Jika X adalah tipe array dengan elemen tipe Xe, set M (X) terdiri dari Xe dan elemen M (Xe).
    • (3.7.5) Jika X adalah tipe non-kelas, non-array, set M (X) kosong.
Shafik Yaghmour
sumber
4
Ada proposal untuk memungkinkan agregat memiliki kelas dasar selama itu dapat dibangun secara default, lihat N4404
Shafik Yaghmour
sementara POD mungkin tetap sama, C ++ 14 StandardLayoutType, yang merupakan persyaratan untuk POD, telah berubah sesuai dengan cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 郝海东 冠状 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 terima kasih, saya tidak tahu bagaimana saya melewatkannya, saya akan mencoba memperbarui selama beberapa hari ke depan.
Shafik Yaghmour
Beri tahu saya jika Anda dapat memberikan contoh POD dalam C ++ 14 tetapi tidak dalam C ++ 11 :-) Saya telah memulai daftar detail contoh di: stackoverflow.com/questions/146452/what- are-pod-types-in-c /…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 jadi apa yang terjadi di sini adalah jika kita melihat deskripsi tata letak standar di C ++ 11 dan C ++ 14 yang cocok. Perubahan ini jika diterapkan melalui laporan cacat kembali ke C ++ 14. Jadi ketika saya menulis ini pada awalnya itu benar :-p
Shafik Yaghmour
47

bisa tolong jelaskan aturan-aturan berikut:

Saya akan mencoba:

a) kelas tata letak standar harus memiliki semua anggota data non-statis dengan kontrol akses yang sama

Itu sederhana: semua anggota data non-statis harus semua menjadi public, privateatau protected. Anda tidak dapat memiliki beberapa publicdan beberapa private.

Alasan untuk mereka pergi ke alasan untuk memiliki perbedaan antara "tata letak standar" dan "bukan tata letak standar" sama sekali. Yaitu, memberi kompiler kebebasan untuk memilih cara memasukkan sesuatu ke dalam memori. Ini bukan hanya tentang petunjuk vtable.

Kembali ketika mereka menstandarisasi C ++ di 98, pada dasarnya mereka harus memprediksi bagaimana orang akan mengimplementasikannya. Sementara mereka memiliki sedikit pengalaman implementasi dengan berbagai rasa C ++, mereka tidak yakin tentang hal-hal. Jadi mereka memutuskan untuk berhati-hati: memberi kompiler kebebasan sebanyak mungkin.

Itu sebabnya definisi POD dalam C ++ 98 sangat ketat. Ini memberikan kompiler C ++ lintang yang besar pada tata letak anggota untuk sebagian besar kelas. Pada dasarnya, tipe POD dimaksudkan sebagai kasus khusus, sesuatu yang Anda tulis secara khusus karena suatu alasan.

Ketika C ++ 11 sedang dikerjakan, mereka memiliki lebih banyak pengalaman dengan kompiler. Dan mereka menyadari bahwa ... Penulis kompiler C ++ benar-benar malas. Mereka memiliki semua kebebasan ini, tetapi mereka tidak melakukan apa-apa dengan itu.

Aturan tata letak standar lebih atau kurang mengodifikasi praktik umum: kebanyakan kompiler tidak benar-benar harus banyak berubah jika ada cara untuk mengimplementasikannya (di luar mungkin beberapa hal untuk sifat tipe yang sesuai).

Sekarang, ketika datang ke public/ private, semuanya berbeda. Kebebasan untuk menyusun ulang anggota mana yang publicvs privatesebenarnya dapat berarti bagi kompiler, khususnya dalam pembuatan debug. Dan karena titik tata letak standar adalah bahwa ada kompatibilitas dengan bahasa lain, Anda tidak dapat memiliki tata letak yang berbeda dalam debug vs rilis.

Lalu ada fakta bahwa itu tidak benar-benar merugikan pengguna. Jika Anda membuat kelas enkapsulasi, kemungkinan besar semua anggota data Anda privatetetap akan melakukannya. Anda biasanya tidak mengekspos anggota data publik pada tipe yang dienkapsulasi sepenuhnya. Jadi ini hanya akan menjadi masalah bagi beberapa pengguna yang ingin melakukan itu, yang menginginkan pembagian itu.

Jadi bukan kerugian besar.

b) hanya satu kelas di seluruh pohon warisan yang dapat memiliki anggota data non-statis,

Alasan untuk ini kembali ke mengapa mereka menstandarisasi tata letak standar lagi: praktik umum.

Tidak ada praktik umum dalam hal memiliki dua anggota pohon warisan yang benar-benar menyimpan barang. Beberapa menempatkan kelas dasar sebelum diturunkan, yang lain melakukannya dengan cara lain. Di mana Anda memesan anggota jika mereka berasal dari dua kelas dasar? Dan seterusnya. Kompiler sangat berbeda pada pertanyaan-pertanyaan ini.

Juga, berkat aturan nol / satu / tak terhingga, begitu Anda mengatakan Anda dapat memiliki dua kelas dengan anggota, Anda dapat mengatakan sebanyak yang Anda inginkan. Ini membutuhkan penambahan banyak aturan tata letak untuk cara menangani ini. Anda harus mengatakan bagaimana multiple inheritance bekerja, kelas mana yang meletakkan data mereka di depan kelas lain, dll. Itu banyak aturan, untuk keuntungan materi yang sangat sedikit.

Anda tidak dapat membuat semua yang tidak memiliki fungsi virtual dan tata letak standar konstruktor default.

dan anggota data non-statis pertama tidak boleh dari tipe kelas dasar (ini bisa melanggar aturan alias).

Saya tidak bisa berbicara dengan yang ini. Saya tidak cukup terdidik dalam aturan aliasing C ++ untuk benar-benar memahaminya. Tapi itu ada hubungannya dengan fakta bahwa anggota pangkalan akan berbagi alamat yang sama dengan kelas dasar itu sendiri. Itu adalah:

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

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

Dan itu mungkin melanggar aturan aliasing C ++. Dalam beberapa cara.

Namun, pertimbangkan ini: bagaimana berguna bisa memiliki kemampuan untuk melakukan hal ini pernah benar-benar terjadi? Karena hanya satu kelas yang dapat memiliki anggota data non-statis, maka Derivedharus kelas itu (karena memiliki Basesebagai anggota). Jadi Base harus kosong (data). Dan jika Basekosong, serta kelas dasar ... mengapa memiliki data anggota itu sama sekali?

Karena Basekosong, tidak memiliki keadaan. Jadi setiap fungsi anggota non-statis akan melakukan apa yang mereka lakukan berdasarkan parameter mereka, bukan thispenunjuk mereka .

Jadi sekali lagi: tidak ada kerugian besar.

Nicol Bolas
sumber
Terima kasih atas penjelasannya, ini sangat membantu. Mungkin meskipun static_cast<Base*>(&d)dan &d.badalah Base*tipe yang sama , mereka menunjuk ke hal-hal yang berbeda sehingga melanggar aturan alias. Tolong perbaiki saya.
Andriy Tylychko
1
dan, mengapa jika hanya satu kelas yang dapat memiliki anggota data non-statis, maka Derivedharus kelas itu?
Andriy Tylychko
3
@AndyT: Agar Derivedanggota pertama menjadi kelas dasarnya, ia harus memiliki dua hal: kelas dasar, dan anggota . Dan karena hanya satu kelas dalam hierarki yang dapat memiliki anggota (dan masih berupa tata letak standar), ini berarti bahwa kelas dasarnya tidak dapat memiliki anggota.
Nicol Bolas
3
@AndyT, Ya, Anda pada dasarnya benar, IME, tentang aturan aliasing. Diperlukan dua contoh berbeda dari jenis yang sama untuk memiliki alamat memori yang berbeda. (Ini memungkinkan pelacakan identitas objek dengan alamat memori.) Objek dasar dan anggota turunan pertama adalah instance yang berbeda, sehingga mereka harus memiliki alamat yang berbeda, yang memaksa padding untuk ditambahkan, mempengaruhi tata letak kelas. Jika mereka dari tipe yang berbeda, itu tidak masalah; objek dengan tipe yang berbeda diizinkan memiliki alamat yang sama (kelas dan anggota data pertamanya, misalnya).
Adam H. Peterson
46

Perubahan dalam C ++ 17

Unduh draft final C ++ 17 International Standard di sini .

Agregat

C ++ 17 memperluas dan meningkatkan agregat dan inisialisasi agregat. Pustaka standar juga sekarang menyertakan std::is_aggregatekelas tipe sifat. Berikut adalah definisi formal dari bagian 11.6.1.1 dan 11.6.1.2 (referensi internal dihilangkan):

Agregat adalah array atau kelas dengan
- tanpa konstruktor yang disediakan pengguna, eksplisit, atau diwariskan,
- tidak ada anggota data non-statis pribadi atau yang dilindungi,
- tanpa fungsi virtual, dan
- tanpa kelas dasar virtual, pribadi, atau dilindungi.
[Catatan: Inisialisasi agregat tidak memungkinkan mengakses anggota atau konstruktor kelas dasar yang dilindungi dan pribadi. —Catat catatan]
Elemen agregat adalah:
- untuk larik, elemen larik dalam urutan subskrip yang meningkat, atau
- untuk kelas, kelas dasar langsung dalam urutan deklarasi, diikuti oleh anggota data non-statis langsung yang tidak anggota serikat anonim, dalam urutan deklarasi.

Apa yang berubah?

  1. Agregat sekarang dapat memiliki kelas dasar publik, non-virtual. Lebih lanjut, ini bukan keharusan bahwa kelas dasar menjadi agregat. Jika tidak agregat, mereka diinisialisasi daftar.
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. Konstruktor default gagal dianulir
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Konstruktor pewarisan tidak diizinkan
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Kelas Sepele

Definisi kelas sepele dikerjakan ulang dalam C ++ 17 untuk mengatasi beberapa cacat yang tidak dibahas dalam C ++ 14. Perubahannya bersifat teknis. Berikut adalah definisi baru di 12.0.6 (referensi internal dihilangkan):

Kelas yang dapat disalin sepele adalah kelas:
- di mana setiap konstruktor salin, konstruktor pemindahan, operator penugasan salin, dan operator penugasan memindahkan dihapus atau sepele,
- yang memiliki setidaknya satu konstruktor salinan yang tidak terhapus, konstruktor bergerak, operator penugasan salin, atau memindahkan operator penugasan, dan
- yang memiliki destruktor yang sepele dan tidak terhapus.
Kelas trivial adalah kelas yang dapat disalin secara trivial dan memiliki satu atau lebih konstruktor default, yang semuanya adalah sepele atau dihapus dan setidaknya satu di antaranya tidak dihapus. [Catatan: Secara khusus, kelas yang dapat disalin atau sepele secara sepele tidak memiliki fungsi virtual atau kelas dasar virtual. — catatan akhir]

Perubahan:

  1. Di bawah C ++ 14, untuk kelas menjadi sepele, kelas tidak bisa memiliki operator copy / move konstruktor / tugas yang non-sepele. Namun, maka yang dinyatakan secara implisit sebagai konstruktor / operator default bisa non-sepele dan belum didefinisikan sebagai dihapus karena, misalnya, kelas berisi sub-objek jenis kelas yang tidak dapat disalin / dipindahkan. Kehadiran konstruktor / operator yang non-sepele, didefinisikan sebagai dihapus akan menyebabkan seluruh kelas menjadi non-sepele. Masalah serupa terjadi dengan destruktor. C ++ 17 mengklarifikasi bahwa keberadaan konstruktor / operator tersebut tidak menyebabkan kelas tidak dapat disalin sepele, karenanya tidak sepele, dan bahwa kelas yang dapat disalin sepele harus memiliki destruktor yang sepele dan tidak terhapus. DR1734 , DR1928
  2. C ++ 14 memungkinkan kelas yang dapat disalin secara sepele, karenanya merupakan kelas sepele, untuk membuat setiap operator copy / move konstruktor / penugasan dinyatakan sebagai dihapus. Jika kelas seperti itu juga tata letak standar, namun dapat disalin / dipindahkan secara legal std::memcpy. Ini adalah kontradiksi semantik, karena, dengan mendefinisikan sebagai menghapus semua operator konstruktor / penugasan, pencipta kelas jelas bermaksud bahwa kelas tidak dapat disalin / dipindahkan, namun kelas masih memenuhi definisi kelas yang dapat ditiru secara sepele. Oleh karena itu dalam C ++ 17 kami memiliki klausa baru yang menyatakan bahwa kelas yang dapat disalin secara sepele harus memiliki setidaknya satu operator penyalin / pemindah copy / pemindahan yang sepele (walaupun tidak selalu dapat diakses publik). Lihat N4148 , DR1734
  3. Perubahan teknis ketiga menyangkut masalah yang sama dengan konstruktor default. Di bawah C ++ 14, sebuah kelas bisa memiliki konstruktor standar sepele yang secara implisit didefinisikan sebagai dihapus, namun masih menjadi kelas sepele. Definisi baru ini mengklarifikasi bahwa kelas sepele harus memiliki setidaknya satu konstruktor default sepele, tidak dihapus. Lihat DR1496

Kelas tata letak standar

Definisi tata letak standar juga dikerjakan ulang untuk mengatasi laporan cacat. Lagi-lagi perubahan bersifat teknis. Ini adalah teks dari standar (12.0.7). Seperti sebelumnya, referensi internal dihilangkan:

Kelas S adalah kelas tata letak standar jika:
- tidak memiliki anggota data non-statis dari tipe kelas non-standar-tata letak (atau array jenis seperti itu) atau referensi,
- tidak memiliki fungsi virtual dan tidak ada kelas dasar virtual,
- memiliki kontrol akses yang sama untuk semua anggota data non-statis,
- tidak memiliki kelas dasar tata letak non-standar,
- memiliki paling banyak satu sub-objek kelas dasar dari jenis apa pun,
- memiliki semua anggota data non-statis dan bidang bit di kelas dan kelas dasarnya dideklarasikan pertama kali di kelas yang sama, dan
- tidak memiliki elemen himpunan M (S) tipe (didefinisikan di bawah) sebagai kelas dasar.108
M (X) didefinisikan sebagai berikut:
- Jika X adalah tipe kelas non-serikat tanpa anggota data non-statis (mungkin diwariskan), set M (X) kosong.
- Jika X adalah tipe kelas non-union yang anggota data non-statis pertamanya memiliki tipe X0 (di mana anggota tersebut mungkin merupakan anonim union), himpunan M (X) terdiri dari X0 dan elemen M (X0).
- Jika X adalah tipe gabungan, set M (X) adalah gabungan dari semua M (Ui) dan set yang berisi semua Ui, di mana masing-masing Ui adalah tipe dari anggota data non-statis engan X.
- Jika X adalah tipe array dengan elemen tipe Xe, himpunan M (X) terdiri dari Xe dan elemen M (Xe).
- Jika X adalah tipe non-kelas, non-array, set M (X) kosong.
[Catatan: M (X) adalah himpunan jenis semua sub-objek non-kelas-dasar yang dijamin dalam kelas tata letak standar berada pada nol diimbangi dalam X. —tulis catatan]
[Contoh:

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
—Mengakhiri contoh]
108) Ini memastikan bahwa dua sub-objek yang memiliki tipe kelas yang sama dan milik objek yang paling banyak diturunkan tidak dialokasikan pada alamat yang sama.

Perubahan:

  1. Mengklarifikasi bahwa persyaratan bahwa hanya satu kelas di pohon derivasi "memiliki" anggota data non-statis mengacu pada kelas di mana anggota data tersebut pertama kali dideklarasikan, bukan kelas di mana mereka dapat diwarisi, dan memperluas persyaratan ini ke bidang bit non-statis . Juga mengklarifikasi bahwa kelas tata letak standar "memiliki paling banyak satu objek kelas dasar dari jenis apa pun yang diberikan." Lihat DR1813 , DR1881
  2. Definisi tata letak standar tidak pernah mengizinkan tipe kelas dasar apa pun menjadi tipe yang sama dengan anggota data non-statis pertama. Ini untuk menghindari situasi di mana anggota data pada offset nol memiliki tipe yang sama dengan kelas dasar mana pun. Standar C ++ 17 memberikan definisi rekursif yang lebih ketat dan berulang tentang "himpunan jenis semua sub-proyek non-kelas-dasar yang dijamin dalam kelas tata letak standar berada pada nol offset" sehingga melarang jenis-jenis tersebut dari menjadi jenis kelas dasar. Lihat DR1672 , DR2120 .

Catatan: Komite standar C ++ menginginkan perubahan di atas berdasarkan pada laporan cacat untuk diterapkan ke C ++ 14, meskipun bahasa baru tidak dalam standar C ++ 14 yang diterbitkan. Itu adalah dalam standar C ++ 17.

ThomasMcLeod
sumber
Catatan Saya baru saja memperbarui jawaban saya bahwa cacat perubahan tata letak standar memiliki status CD4 yang berarti mereka benar-benar diterapkan ke C ++ 14. Itu sebabnya jawaban saya tidak termasuk mereka. Ini terjadi setelah saya menulis jawaban saya.
Shafik Yaghmour
Catatan, saya memulai hadiah untuk pertanyaan ini.
Shafik Yaghmour
Terima kasih @ShafikYaghmour. Saya akan meninjau status laporan cacat dan memodifikasi jawaban saya.
ThomasMcLeod
@ShafikYaghmour, Setelah meninjau proses C ++ 14 dan bagi saya tampak bahwa, sementara DR ini "diterima" pada pertemuan Juni 2014 Rapperswil, bahasa dari pertemuan Issaquah Februari 2014 adalah apa yang menjadi C ++ 14. Lihat isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "sesuai dengan aturan ISO, kami tidak secara resmi menyetujui pengeditan apa pun pada kertas kerja C ++." Apakah saya melewatkan sesuatu?
ThomasMcLeod
Mereka memiliki status 'CD4' yang berarti mereka harus menerapkan dalam mode C ++ 14.
Shafik Yaghmour
14

Apa yang berubah

Mengikuti sisa tema yang jelas dari pertanyaan ini, makna dan penggunaan agregat terus berubah dengan setiap standar. Ada beberapa perubahan kunci di cakrawala.

Jenis dengan P1008 konstruktor yang dinyatakan pengguna

Di C ++ 17, tipe ini masih agregat:

struct X {
    X() = delete;
};

Dan karenanya, X{}masih mengkompilasi karena itu adalah inisialisasi agregat - bukan doa konstruktor. Lihat juga: Kapan konstruktor pribadi bukan konstruktor pribadi?

Di C ++ 20, batasan akan berubah dari yang membutuhkan:

tidak ada yang disediakan pengguna explicit,, atau konstruktor yang diwariskan

untuk

tidak ada konstruktor yang dideklarasikan atau diwariskan pengguna

Ini telah diadopsi ke dalam draft kerja C ++ 20 . Baik di Xsini maupun Cdi dalam pertanyaan terkait akan menjadi agregat dalam C ++ 20.

Ini juga membuat efek yo-yo dengan contoh berikut:

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

Dalam C ++ 11/14, Bitu bukan agregat karena kelas dasar, jadi B{}melakukan inisialisasi nilai yang memanggil panggilan B::B()mana A::A(), pada titik di mana ia dapat diakses. Ini terbentuk dengan baik.

Dalam C ++ 17, Bmenjadi agregat karena kelas dasar diizinkan, yang membuat B{}inisialisasi agregat. Ini memerlukan inisialisasi daftar salin Adari {}, tetapi dari luar konteks B, di mana ia tidak dapat diakses. Dalam C ++ 17, ini salah bentuk (auto x = B(); akan baik-baik saja).

Dalam C ++ 20 sekarang, karena perubahan aturan di atas, Bsekali lagi berhenti menjadi agregat (bukan karena kelas dasar, tetapi karena konstruktor default yang dideklarasikan pengguna - meskipun default). Jadi kita kembali ke masa laluB konstruktor, dan cuplikan ini menjadi bagus.

Menginisialisasi agregat dari daftar nilai tanda kurung P960

Masalah umum yang muncul adalah ingin menggunakan emplace()konstruktor gaya dengan agregat:

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

Ini tidak berfungsi, karena emplaceakan mencoba melakukan inisialisasi secara efektif X(1, 2), yang tidak valid. Solusi tipikalnya adalah menambahkan konstruktor X, tetapi dengan proposal ini (saat ini sedang mengerjakan Core), agregat akan secara efektif telah mensintesis konstruktor yang melakukan hal yang benar - dan berperilaku seperti konstruktor biasa. Kode di atas akan dikompilasi seperti apa adanya di C ++ 20.

Pengurangan Argumen Templat Kelas (CTAD) untuk Agregat P1021 (khusus P1816 )

Di C ++ 17, ini tidak mengkompilasi:

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

Point p{1, 2}; // error

Pengguna harus menulis panduan pengurangan sendiri untuk semua templat agregat:

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

Tetapi karena ini dalam arti tertentu "hal yang jelas" untuk dilakukan, dan pada dasarnya hanya omong kosong, bahasa akan melakukan ini untuk Anda. Contoh ini akan dikompilasi dalam C ++ 20 (tanpa perlu panduan deduksi yang disediakan pengguna).

Barry
sumber
Meskipun saya akan memperbaiki, rasanya agak terlalu dini untuk menambahkan ini, saya tidak tahu apa-apa turun besar yang akan mengubah ini sebelum C ++ 2x dilakukan.
Shafik Yaghmour
@ShafikYaghmour Ya, mungkin WAY terlalu dini. Tetapi mengingat bahwa SD adalah batas waktu untuk fitur bahasa baru, ini adalah dua-satunya dalam penerbangan yang saya ketahui - kasus terburuk saya hanya memblokir-menghapus salah satu bagian ini nanti? Saya baru saja melihat pertanyaan aktif dengan karunia dan berpikir itu adalah waktu yang tepat untuk berpadu sebelum saya lupa.
Barry
Saya mengerti, saya telah tergoda beberapa kali untuk kasus serupa. Saya selalu khawatir sesuatu yang besar akan berubah dan akhirnya saya harus menulis ulang.
Shafik Yaghmour
@ShafikYaghmour Sepertinya tidak ada yang akan berubah di sini :)
Barry
Saya harap ini diperbarui sekarang, dengan C ++ 20 sudah dirilis
Noone AtAll