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?
Ini FAQ adalah tentang Agregat dan PODS dan mencakup materi berikut:
Jawaban:
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:
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 class
merujuk 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 - m
elemen 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 0
untuk jenis yang ( false
untuk bool
, 0.0
untuk 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.c
diinisialisasi dengan 'a'
, y.x.i1
dengan 10
, y.x.i2
dengan 20
, y.i[0]
dengan 20
, y.i[1]
dengan 30
dan y.f
diinisialisasi-nilai, yaitu diinisialisasi dengan 0.0
. Anggota statis yang dilindungi d
tidak 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
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 ?)
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 memcpy
isi objek Anda menjadi array char atau char unsigned, dan kemudian memcpy
konten 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_cast
dari A*
ke T*
dan mendapatkan pointer ke anggota pertama dan sebaliknya.
Daftar ini terus dan terus ...
Penting untuk memahami apa sebenarnya POD itu karena banyak fitur bahasa, seperti yang Anda lihat, berperilaku berbeda untuk mereka.
private:
sesuai): struct A { int const a; };
maka A()
terbentuk dengan baik, bahkan jika A
definisi konstruktor default akan menjadi buruk.
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?
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.
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.
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:
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.
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), memcpy
boleh 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 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
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;
Kita dapat merujuk ke standar Draft C ++ 14 untuk referensi.
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.
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.
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.
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
, private
atau protected
. Anda tidak dapat memiliki beberapa public
dan 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 public
vs private
sebenarnya 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 private
tetap 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 Derived
harus kelas itu (karena memiliki Base
sebagai anggota). Jadi Base
harus kosong (data). Dan jika Base
kosong, serta kelas dasar ... mengapa memiliki data anggota itu sama sekali?
Karena Base
kosong, tidak memiliki keadaan. Jadi setiap fungsi anggota non-statis akan melakukan apa yang mereka lakukan berdasarkan parameter mereka, bukan this
penunjuk mereka .
Jadi sekali lagi: tidak ada kerugian besar.
static_cast<Base*>(&d)
dan &d.b
adalah Base*
tipe yang sama , mereka menunjuk ke hal-hal yang berbeda sehingga melanggar aturan alias. Tolong perbaiki saya.
Derived
harus kelas itu?
Derived
anggota 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.
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_aggregate
kelas 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?
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
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
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:
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 , DR1734Kelas 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:
—Mengakhiri 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
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:
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.
Mengikuti sisa tema yang jelas dari pertanyaan ini, makna dan penggunaan agregat terus berubah dengan setiap standar. Ada beberapa perubahan kunci di cakrawala.
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 X
sini maupun C
di 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, B
itu 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, B
menjadi agregat karena kelas dasar diizinkan, yang membuat B{}
inisialisasi agregat. Ini memerlukan inisialisasi daftar salin A
dari {}
, 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, B
sekali 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.
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 emplace
akan 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.
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).