Bu SSS , Agregalar ve POD'lar ile ilgilidir ve aşağıdaki malzemeleri kapsar:
- Nelerdir Agregalar ?
- Nelerdir POD s (Düz Eski Veriler)?
- İlişkileri nasıl?
- Nasıl ve neden özel?
- C ++ 11 için ne değişir?
Bu SSS , Agregalar ve POD'lar ile ilgilidir ve aşağıdaki malzemeleri kapsar:
Yanıtlar:
Bu makale oldukça uzun. Hem agregaları hem de POD'ları (Düz Eski Veriler) bilmek istiyorsanız, zaman ayırın ve okuyun. Sadece agregalarla ilgileniyorsanız, sadece ilk kısmı okuyun. Yalnızca PODs ilgilenen varsa o zaman ilk agrega tanımı, etkileri ve örnekler okumalı ve sonra olabilecek PODs atlamak ama hala kendi bütünlüğü içinde birinci bölümünü okuduğunuz öneriyoruz. Agrega kavramı POD'ları tanımlamak için gereklidir. Herhangi bir hata bulursanız (dilbilgisi, üslup, biçimlendirme, sözdizimi vb. Dahil olmak üzere küçük bile olsa) lütfen yorum bırakın, düzenleyeceğim.
Bu cevap C ++ 03 için geçerlidir. Diğer C ++ standartları için bakınız:
C ++ standardından biçimsel tanım ( C ++ 03 8.5.1 §1 ) :
Toplama, kullanıcı tarafından bildirilen kurucuları (12.1), özel veya korumalı statik olmayan veri üyeleri (madde 11), temel sınıflar (madde 10) ve sanal işlevler (10.3) bulunmayan bir dizi veya sınıftır (madde 9). ).
Tamam, bu tanımı ayrıştıralım. Her şeyden önce, herhangi bir dizi bir kümedir. Bir sınıf aynı zamanda… bekle! yapılar veya sendikalar hakkında hiçbir şey söylenmez, bunlar küme olamazlar mı? Evet yapabilirler. C ++ 'da, terim class
tüm sınıfları, yapıları ve birlikleri ifade eder. Dolayısıyla, bir sınıf (ya da yapı ya da birlik), yalnızca yukarıdaki tanımlardan gelen kriterleri karşıladığında toplanır. Bu kriterler ne anlama geliyor?
Bu, bir toplu sınıfın yapıcılara sahip olamayacağı anlamına gelmez, aslında derleyici tarafından açıkça belirtildiği ve kullanıcı tarafından açıkça belirtilmediği sürece varsayılan bir kurucuya ve / veya bir kopya kurucuya sahip olabileceği anlamına gelmez.
Özel veya korumalı statik olmayan veri üyeleri yok . Toplama sınıflarının kurallarını ihlal ettiğiniz gibi istediğiniz kadar özel ve korumalı üye işlevinizin (ancak yapıcıların değil) yanı sıra istediğiniz kadar özel veya korumalı statik veri üyesine ve üye işlevine sahip olabilirsiniz
Bir toplu sınıf, kullanıcı tarafından bildirilen / kullanıcı tanımlı bir kopya atama işleci ve / veya yıkıcıya sahip olabilir
Bir dizi, birleştirilmiş olmayan sınıf türü dizisi olsa bile bir toplamdır.
Şimdi bazı örneklere bakalım:
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
};
Kaptın bu işi. Şimdi toplamların nasıl özel olduğunu görelim. Agrega olmayan sınıflardan farklı olarak, kıvırcık parantezlerle başlatılabilirler {}
. Bu başlatma sözdizimi, diziler için yaygın olarak bilinir ve bunların toplamlar olduğunu öğrendik. Öyleyse onlarla başlayalım.
Type array_name[n] = {a1, a2, …, am};
(m == n) ise
i inci dizinin elemanı ile başlatılır ı
başka, eğer (m, <n)
dizinin ilk m elemanları ile başlatılır 1 , bir 2 , ..., bir m ve diğern - m
elemanlar mümkünse vardır değer başlatıldı (terimin açıklama için aşağıya bakınız)
else if (m> n)
bir hata mesajı üretir derleyici
başka (bu durumda olduğunda n gibi hiç belirtilmemişse int a[] = {1, 2, 3};
)
boyutunu dizi (n), böylece buna eşit olduğu varsayılırint a[] = {1, 2, 3};
eşdeğerdirint a[3] = {1, 2, 3};
Skalar tip (bir amacı zaman bool
, int
, char
, double
, işaretçiler, vs.) değer başlatıldı bunun ile başlatılır anlamına gelir 0
, bu tür için ( false
için bool
, 0.0
için double
, vs.). Kullanıcı tarafından bildirilen bir varsayılan kurucuya sahip sınıf türünde bir nesne değer-başlatıldığında, varsayılan kurucu çağrılır. Varsayılan kurucu örtük olarak tanımlanmışsa, tüm statik olmayan üyeler özyinelemeli olarak değer başlatılır. Bu tanım kesin değildir ve biraz yanlıştır, ancak size temel fikri vermelidir. Bir başvuru, değer başlatılamaz. Birleştirilmiş olmayan bir sınıf için değer başlatma, örneğin sınıfın uygun bir varsayılan kurucusu yoksa başarısız olabilir.
Dizi başlatma örnekleri:
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
}
Şimdi toplu sınıfların kaşlı ayraçlarla nasıl başlatılabileceğini görelim. Hemen hemen aynı şekilde. Dizi elemanları yerine, statik olmayan veri üyelerini sınıf tanımındaki görünümlerine göre başlatacağız (hepsi tanım gereği herkese açıktır). Üyelerden daha az başlatıcı varsa, geri kalanı değerle başlatılır. Açık bir şekilde başlatılmamış üyelerden birine değer vermek mümkün değilse, derleme zamanı hatası alırız. Gerekenden daha fazla başlatıcı varsa, derleme zamanı hatası da alırız.
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}};
Yukarıdaki örnekte y.c
, ile 'a'
, y.x.i1
ile 10
, y.x.i2
ile 20
, y.i[0]
ile 20
, y.i[1]
ile 30
ve y.f
değerle başlatılır, yani ile başlatılır 0.0
. Korumalı statik üye d
hiç başlatılmaz, çünkü öyle static
.
Toplu sendikalar farklıdır, çünkü yalnızca ilk üyelerini parantezle başlatabilirsiniz. C ++ 'da sendika kullanmayı düşünecek kadar gelişmişseniz (kullanımları çok tehlikeli olabilir ve dikkatlice düşünülmelidir), standarttaki sendikaların kurallarına kendiniz bakabilirsiniz :).
Toplamlar hakkında neyin özel olduğunu bildiğimize göre, sınıflardaki kısıtlamaları anlamaya çalışalım; işte bu yüzden neden oradalar. Parantez ile üye olarak başlatmanın, sınıfın üyelerinin toplamından başka bir şey olmadığını ima ettiğini anlamalıyız. Kullanıcı tanımlı bir kurucu varsa, kullanıcının üyeleri başlatmak için fazladan bir iş yapması gerektiği anlamına gelir, bu nedenle küme ayracı başlatma yanlış olur. Sanal işlevler varsa, bu sınıftaki nesnelerin (çoğu uygulamada) yapıcıda ayarlanan sınıfın vtable'ı için bir işaretçi olduğu anlamına gelir, bu nedenle küme parantezinin başlatılması yetersiz olacaktır. Geri kalan kısıtlamaları bir egzersize benzer şekilde anlayabilirsiniz :).
Agregalar hakkında yeterli. Şimdi, POD'lara daha katı türler tanımlayabiliriz
C ++ standardından biçimsel tanım ( C ++ 03 9 §4 ) :
POD-yapı, POD-struct olmayan, POD-birliği olmayan (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyelerine sahip olmayan ve kullanıcı tanımlı bir kopya atama operatörü olmayan ve hiçbir kullanıcı tanımlı yıkıcı. Benzer şekilde, POD-birliği, POD-yapı olmayan, POD-birliği olmayan (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyelerine sahip olmayan ve kullanıcı tanımlı bir kopya atama operatörü olmayan bir toplu birliktir ve kullanıcı tanımlı yıkıcı yok. POD sınıfı, POD yapısı veya POD birleşimi olan bir sınıftır.
Vay canına, bu ayrıştırmak daha zor, değil mi? :) Sendikaları dışarıda bırakalım (yukarıdakiyle aynı gerekçelerle) ve biraz daha açık bir şekilde yeniden ifade edelim:
Kullanıcı sınıfı tanımlı bir kopya atama işleci ve yıkıcısı yoksa ve statik olmayan üyelerinden hiçbiri POD olmayan bir sınıf, POD olmayan bir dizi veya bir başvuru değilse, toplu sınıf POD olarak adlandırılır.
Bu tanım ne anlama geliyor? ( POD'un Eski Düz Veri anlamına geldiğinden bahsetmiş miydim ?)
Örnekler:
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
};
POD sınıfları, POD-birlikleri, skaler tipler ve bu tipteki diziler topluca POD-tipleri olarak adlandırılır .
POD'lar birçok açıdan özeldir. Sadece bazı örnekler vereceğim.
POD sınıfları C yapılarına en yakın olanlardır. Bunlardan farklı olarak, POD'larda üye işlevleri ve rastgele statik üyeler olabilir, ancak bu ikisinden hiçbiri nesnenin bellek düzenini değiştirmez. Bu nedenle, C ve hatta .NET'ten kullanılabilecek az çok taşınabilir bir dinamik kitaplık yazmak istiyorsanız, dışa aktarılan tüm işlevlerinizi yalnızca POD tipi parametrelerini alıp döndürmeye çalışmalısınız.
POD sınıfı olmayan türdeki nesnelerin ömrü, kurucu tamamlandığında başlar ve yıkıcı tamamlandığında sona erer. POD sınıfları için ömür, nesne için depolama alanı dolduğunda başlar ve bu depolama alanı bırakıldığında veya yeniden kullanıldığında sona erer.
POD türündeki nesneler için, standart tarafından, memcpy
nesnenizin içeriğini bir char veya unsigned char dizisine ve daha memcpy
sonra içeriğin nesnenize geri döndüğünde, nesnenin orijinal değerini koruyacağı garanti edilir . POD olmayan türler için böyle bir garanti olmadığını unutmayın. Ayrıca, POD nesnelerini ile güvenle kopyalayabilirsiniz memcpy
. Aşağıdaki örnekte T'nin POD tipi olduğu varsayılmaktadır:
#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
ifadeye git. Bildiğiniz gibi, bazı değişkenlerin henüz kapsamda olmadığı bir noktadan halihazırda kapsamda olan bir noktaya goto aracılığıyla bir sıçrama yapmak yasadışıdır (derleyici bir hata vermelidir). Bu kısıtlama yalnızca değişken POD olmayan türdeyse geçerlidir. Aşağıdaki örnekte iyi f()
biçimlendirilmiş, kötü g()
biçimlendirilmiş. Microsoft'un derleyicisinin bu kuralla çok liberal olduğunu unutmayın; her iki durumda da bir uyarı verir.
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;
}
Bir POD nesnesinin başlangıcında dolgu olmayacağı garanti edilir. POD sınıfı A'nın birinci eleman türü T ise, başka bir deyişle, güvenli bir şekilde olabilir reinterpret_cast
gelen A*
için T*
işaretçi ve elde tersi birinci eleman ve yardımcısı.
Liste uzayıp gidiyor…
Bir POD'un tam olarak ne olduğunu anlamak önemlidir, çünkü gördüğünüz gibi birçok dil özelliği onlar için farklı davranır.
private:
uygun olarak): struct A { int const a; };
o zaman A()
bile, iyi oluşturulmuş olan A
bireyin varsayılan yapıcı tanımı kötü şekillendirilmiş olur.
Bir agreganın standart tanımı biraz değişti, ancak yine de hemen hemen aynı:
Toplama, kullanıcı tarafından sağlanan kurucuları (12.1), statik olmayan veri üyeleri için küme ayracı veya eşit başlatıcıları olmayan (9.2), özel veya korumalı statik olmayan veri üyeleri olmayan bir dizi veya sınıftır (Madde 9). Madde 11), temel sınıflar (Madde 10) ve sanal işlevler (10.3) yoktur.
Tamam, ne değişti?
Önceden, bir topluluğun kullanıcı tarafından bildirilen kurucuları olamazdı , ancak şimdi kullanıcı tarafından sağlanan kuruculara sahip olamaz . Bir fark var mı? Evet, var, çünkü şimdi yapıcıları bildirebilir ve varsayılan yapabilirsiniz :
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
İlk bildirimde varsayılan olarak ayarlanan bir kurucu (veya herhangi bir özel üye işlevi) kullanıcı tarafından sağlanmadığından, bu yine de bir toplamdır .
Artık bir toplamanın statik olmayan veri üyeleri için herhangi bir küme ayracı veya eşit başlatıcısı olamaz . Ne anlama geliyor? Bunun nedeni, bu yeni standartla üyeleri doğrudan sınıfta şöyle başlatabilmemizdir:
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
Bu özelliği kullanmak, sınıfı artık birleştirilmiş hale getirmez, çünkü temel olarak kendi varsayılan kurucunuzu sağlamaya eşdeğerdir.
Yani, toplam olan şey pek değişmedi. Yeni özelliklere uyarlanmış hala aynı temel fikir.
POD'lar birçok değişiklik geçirdi. POD'lar hakkında önceki kuralların birçoğu bu yeni standartta gevşetildi ve standartta tanımın sunulma şekli kökten değiştirildi.
Bir POD fikri temel olarak iki farklı özelliği yakalamaktır:
Bu nedenle, tanım iki farklı kavrama ayrılmıştır: önemsiz sınıflar ve standart düzen sınıfları, çünkü bunlar POD'dan daha kullanışlıdır. Standart artık nadiren POD terimini kullanıyor ve daha özel önemsiz ve standart düzen kavramlarını tercih ediyor .
Yeni tanım temelde bir POD'un hem önemsiz hem de standart düzeni olan bir sınıf olduğunu ve bu özelliğin tüm statik olmayan veri üyeleri için özyinelemeli olması gerektiğini söylüyor:
POD yapısı, hem önemsiz bir sınıf hem de standart mizanpaj sınıfı olan ve POD olmayan yapı, POD olmayan birleşme (veya bu tür bir dizi) türünde statik olmayan veri üyelerine sahip olmayan birleşik sınıftır. Benzer şekilde, bir POD birliği, hem önemsiz bir sınıf hem de standart bir düzen sınıfı olan ve POD olmayan yapı, POD olmayan birleşme (veya bu tür bir dizi) türünde statik olmayan veri üyelerine sahip olmayan bir birliktir. POD sınıfı, POD yapısı veya POD birleşimi olan bir sınıftır.
Bu iki özelliğin her birini ayrı ayrı ayrıntılı olarak inceleyelim.
Önemsiz yukarıda belirtilen ilk özelliktir: önemsiz sınıflar statik başlatmayı destekler. Bir sınıf önemsiz bir şekilde taklit edilebilirse (önemsiz sınıfların bir üst kümesi), yerdeki temsilini benzer şeylerle kopyalamak ve memcpy
sonucun aynı olmasını beklemek uygundur.
Standart önemsiz bir sınıfı aşağıdaki gibi tanımlar:
Önemsiz olarak kopyalanabilen bir sınıf:
- önemsiz olmayan kopya kurucuları yoktur (12.8),
- önemsiz olmayan hareket kurucuları yoktur (12.8),
- önemsiz olmayan kopya atama işleçleri yoktur (13.5.3, 12.8),
- önemsiz olmayan hareket atama operatörleri yok (13.5.3, 12.8) ve
- önemsiz bir yıkıcıya sahiptir (12.4).
Önemsiz bir sınıf, önemsiz bir varsayılan yapıcıya (12.1) sahip olan ve önemsiz bir şekilde kopyalanabilen bir sınıftır.
[ Not: Özellikle önemsiz bir şekilde kopyalanabilen veya önemsiz bir sınıfın sanal işlevleri veya sanal temel sınıfları yoktur. —End not ]
Peki, tüm bu önemsiz ve önemsiz şeyler nelerdir?
X sınıfı için bir kopyala / taşı yapıcısı, kullanıcı tarafından sağlanmadıysa ve
- X sınıfı sanal işlevlere (10.3) ve sanal temel sınıflara (10.1) sahip değildir ve
- her doğrudan temel sınıf alt nesnesini kopyalamak / taşımak için seçilen yapıcı önemsizdir ve
- X'in sınıf tipi (veya dizisi) statik olmayan her veri elemanı için, o üyeyi kopyalamak / taşımak için seçilen yapıcı önemsizdir;
aksi takdirde kopyalama / taşıma yapıcısı önemsiz değildir.
Temel olarak bu, bir copy veya move yapıcısının kullanıcı tarafından sağlanan değilse önemsiz olduğu, sınıfın içinde sanal bir şey olmadığı ve bu özelliğin sınıfın tüm üyeleri ve temel sınıf için özyinelemeli olduğu anlamına gelir.
Önemsiz bir kopyala / taşı atama operatörünün tanımı çok benzerdir, sadece "yapıcı" kelimesini "atama operatörü" ile değiştirir.
Önemsiz bir yıkıcı da benzer bir tanıma sahiptir ve ek kısıtlaması sanal olamaz.
Ve yine, önemsiz varsayılan kurucular için benzer bir kural daha vardır; ayrıca, sınıfta yukarıda gördüğümüz küme ayracı veya eşit başlatıcılara sahip statik olmayan veri üyeleri varsa, varsayılan bir kurucu önemsiz değildir .
Her şeyi temizlemek için bazı örnekler:
// 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
};
Standart mizanpaj ikinci özelliktir. Standart, bunların diğer dillerle iletişim kurmak için yararlı olduğunu belirtmektedir ve bunun nedeni, standart düzen sınıfının, eşdeğer C yapısının veya birleşiminin aynı bellek düzenine sahip olmasıdır.
Bu, üyeler ve tüm temel sınıflar için özyinelemeli olması gereken başka bir özelliktir. Ve her zamanki gibi, sanal işlevlere veya sanal temel sınıflara izin verilmez. Bu, düzeni C ile uyumsuz hale getirir.
Burada rahat bir kural, standart mizanpaj sınıflarının aynı erişim kontrolüne sahip statik olmayan tüm veri üyelerine sahip olması gerektiğidir. Daha önce bunların hepsi herkese açık olmak zorundaydı , ancak şimdi hepsi özel veya hepsi korunmuş olduğu sürece onları özel veya korunmuş yapabilirsiniz .
Kalıtım kullanılırken , tüm kalıtım ağacında yalnızca bir sınıf statik olmayan veri üyelerine sahip olabilir ve ilk statik olmayan veri üyesi temel sınıf türünden (bu diğer adlandırma kurallarını kırabilir) olamaz, aksi takdirde standart düzen sınıfı.
Tanım, standart metinde şu şekildedir:
Standart mizanpaj sınıfı, aşağıdakileri yapan bir sınıftır:
- standart olmayan mizanpaj sınıfı (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyeleri yoksa,
- sanal işlevleri (10.3) ve sanal temel sınıfları (10.1) yoktur,
- statik olmayan tüm veri üyeleri için aynı erişim kontrolüne (Madde 11) sahiptir,
- standart olmayan yerleşim temel sınıfları yoktur,
- ya en çok türetilmiş sınıfta statik olmayan veri üyeleri ve statik olmayan veri üyelerine sahip en fazla bir temel sınıf bulunmaması ya da statik olmayan veri üyelerine sahip temel sınıfların olmaması ve
- ilk statik olmayan veri elemanı ile aynı türde temel sınıflara sahip değildir.
Standart düzen yapısı, sınıf anahtarı yapısı veya sınıf anahtarı sınıfı ile tanımlanan standart düzen sınıfıdır.
Standart mizanpaj birliği, sınıf anahtarı birliği ile tanımlanan standart mizanpaj sınıfıdır.
[ Not: Standart yerleşim sınıfları, diğer programlama dillerinde yazılmış kodlarla iletişim kurmak için kullanışlıdır. Onların düzeni 9.2'de belirtilmiştir. —End not ]
Ve birkaç örnek görelim.
// 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
Bu yeni kurallarla artık çok daha fazla tür POD olabilir. Ve bir tür POD olmasa bile, bazı POD özelliklerinden ayrı olarak yararlanabiliriz (yalnızca önemsiz veya standart yerleşimden biriyse).
Standart kitaplığın başlıkta bu özellikleri test etmek için özellikleri vardır <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;
Referans için Taslak C ++ 14 standardına başvurabiliriz .
Bu, bize aşağıdaki tanımı veren 8.5.1
Agregalar bölümünde ele alınmıştır :
Toplama, kullanıcı tarafından sağlanan kurucular (12.1), özel veya korumalı statik olmayan veri üyeleri (Madde 11), temel sınıflar (Madde 10) ve sanal işlevler (10.3) bulunmayan bir dizi veya sınıftır (Madde 9). ).
Tek değişiklik şimdi sınıf içi üye başlatıcıları eklemek bir sınıfı toplu olmayan bir hale getirmiyor. Dolayısıyla, aşağıdaki örnekte hız içi başlatıcılara sahip sınıflar için C ++ 11 toplam başlatma :
struct A
{
int a = 3;
int b = 3;
};
C ++ 11'de bir toplu değildi, ancak C ++ 14'te. Bu değişiklik N3605 :
Bjarne Stroustrup ve Richard Smith, toplam başlatma ve üye başlatıcıların birlikte çalışmadığı konusunda bir sorun ortaya çıkardı. Bu makale, Smith'in, üye başlatıcılara sahip olamayacağı bir kısıtlamayı ortadan kaldıran Smith'in önerdiği ifadeyi benimseyerek sorunu gidermeyi önermektedir.
POD ( düz eski veriler ) yapısının tanımı, 9
Sınıflar bölümünde anlatılmıştır :
POD yapısı 110 , hem önemsiz bir sınıf hem de standart mizanpaj sınıfı olan ve POD olmayan yapı, POD olmayan birleşme (veya bu tür bir dizi) türünde statik olmayan veri üyelerine sahip olmayan birleşik sınıftır. Benzer şekilde, bir POD birliği hem önemsiz bir sınıf hem de standart mizanpaj sınıfı olan ve POD olmayan yapı, POD olmayan birleşme (veya bu tür bir dizi) türünde statik olmayan veri üyelerine sahip olmayan bir birliktir. POD sınıfı, POD yapısı veya POD birleşimi olan bir sınıftır.
C ++ 11 ile aynı ifadedir.
Yorumlarda belirtildiği gibi, pod standart yerleşimin tanımına dayanmaktadır ve bu C ++ 14 için değişmiştir, ancak bu durumdan sonra C ++ 14'e uygulanan hata raporları yoluyla olmuştur.
Üç DR vardı:
Yani standart düzen bu C ++ 14'ten geçti:
Standart mizanpaj sınıfı, aşağıdakileri yapan bir sınıftır:
- (7.1) standart olmayan mizanpaj sınıfı (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyeleri yoksa,
- (7.2) sanal işlevleri ([class.virtual]) ve sanal temel sınıfları ([class.mi]) yoktur,
- (7.3) statik olmayan tüm veri üyeleri için aynı erişim kontrolüne (Madde [class.access]) sahiptir,
- (7.4) standart olmayan yerleşim temel sınıfları yoktur,
- (7.5) ya en çok türetilmiş sınıfta statik olmayan veri üyeleri ve statik olmayan veri üyelerine sahip en fazla bir temel sınıf yoktur ya da statik olmayan veri üyelerine sahip temel sınıfları yoktur ve
- (7.6) birinci statik olmayan veri elemanı ile aynı tipte temel sınıflara sahip değildir.
Buna C ++ 14'te :
S sınıfı, aşağıdaki durumlarda standart mizanpaj sınıfıdır:
- (3.1) standart olmayan mizanpaj sınıfı (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyeleri yoksa,
- (3.2) sanal işlevleri ve sanal temel sınıfları yoktur,
- (3.3) statik olmayan tüm veri üyeleri için aynı erişim kontrolüne sahiptir,
- (3.4) standart olmayan yerleşim temel sınıflarına sahip değildir,
- (3.5) herhangi bir türden en fazla bir temel sınıf alt nesneye sahip olması,
- (3.6) sınıftaki tüm statik olmayan veri üyelerine ve bit alanlarına ve ilk olarak aynı sınıfta bildirilen temel sınıflara sahiptir ve
- (3.7), baz sınıfı olarak M (S) tipi kümelerin hiçbir elemanına sahip değildir; burada X, M (X) türleri için aşağıdaki gibi tanımlanır. 104 [Not: M (X), X'de sıfır ofsetinde olabilecek taban sınıfı olmayan tüm alt nesneler. - son not]
- (3.7.1) X, statik olmayan veri üyesi olmayan (muhtemelen devralınan) sendikasız sınıf tipindeyse, M (X) kümesi boştur.
- (3.7.2) X, sıfır boyutta olan veya X'in ilk statik olmayan veri üyesi olan X0 türünde statik olmayan bir veri üyesi olan birleşik olmayan sınıf tipiyse (burada adı geçen üye anonim bir birleşim olabilir) ), M (X) grubu X0 ve M (X0) elemanlarından oluşur.
- (3.7.3) X bir birleşim tipiyse, M (X) kümesi tüm M (Ui) ve tüm Ui'leri içeren kümedir, burada her Ui X'in statik olmayan veri elemanının tipidir. .
- (3.7.4) X, Xe eleman tipine sahip bir dizi tipiyse, M (X) grubu Xe ve M (Xe) elemanlarından oluşur.
- (3.7.5) X, sınıf olmayan, dizi olmayan bir türse, M (X) kümesi boştur.
lütfen aşağıdaki kuralları hazırlayabilir misiniz?
Deneyeceğim:
a) standart mizanpaj sınıfları, aynı erişim kontrolüne sahip statik olmayan tüm veri üyelerine sahip olmalıdır
Bu basit: tüm statik olmayan veri üyeleri olmalıdır hepsi olabilir public
, private
ya da protected
. Bazılarına public
ve bazılarına sahip olamazsın private
.
Onlar için akıl yürütme, "standart mizanpaj" ile "standart mizanpaj değil" arasında bir ayrım yapma gerekçesine dayanır. Yani, derleyiciye işleri nasıl hafızaya alabileceğini seçme özgürlüğü vermek. Bu sadece kararlı işaretçilerle ilgili değil.
98'de C ++ standardize ettiklerinde, insanların bunu nasıl uygulayacağını tahmin etmek zorundaydılar. C ++ 'ın çeşitli lezzetleri ile uygulama deneyimi biraz olsa da, konulardan emin değildiler. Bu yüzden temkinli olmaya karar verdiler: derleyicilere mümkün olduğunca çok özgürlük verin.
Bu yüzden C ++ 98'deki POD'un tanımı çok katıdır. Çoğu sınıf için üye düzeninde C ++ derleyicilerine büyük enlem verdi. Temel olarak, POD tiplerinin özel durumlar olması amaçlanmıştır, bir nedenden dolayı özellikle yazdığınız bir şey.
C ++ 11 üzerinde çalışırken derleyicilerle çok daha fazla deneyimleri vardı. Ve ... C ++ derleyici yazarlarının gerçekten tembel olduğunu fark ettiler. Hepsi bu özgürlüğü vardı, ama onlar yoktu yapmak onunla her şeyi.
Standart mizanpaj kuralları az çok kodlayıcı ortak uygulamadır: çoğu derleyici bunları uygulamak için herhangi bir şey olsa bile çok fazla değişmek zorunda değildi (belki de ilgili tip özellikleri için bazı şeyler dışında).
O geldiğinde Şimdi, public
/ private
, durum farklı. Hangi üyelerin public
vs. karşı olduklarını yeniden sıralama özgürlüğü private
, özellikle hata ayıklama yapılarında derleyici için önemli olabilir. Standart mizanpajın amacı diğer dillerle uyumluluk olduğu için, hata ayıklama ve sürümde mizanpajın farklı olmasını sağlayamazsınız.
Sonra kullanıcıya gerçekten zarar vermediği gerçeği var. Kapsüllenmiş bir sınıf oluşturuyorsanız, tüm veri üyelerinizin private
yine de olma ihtimali yüksektir. Genel veri üyelerini genellikle tamamen kapsüllenmiş türlerde göstermezsiniz. Bu sadece bunu yapmak isteyen, bu bölümü isteyen birkaç kullanıcı için bir problem olacaktır.
Yani büyük bir kayıp değil.
b) tüm miras ağacında sadece bir sınıf statik olmayan veri üyelerine sahip olabilir,
Bunun nedeni, standart düzeni neden tekrar standartlaştırdıklarına geri dönüyor: ortak uygulama.
Bir şeyleri saklayan iki miras ağacına sahip olmak söz konusu olduğunda yaygın bir uygulama yoktur . Bazıları temel sınıfı türetilmeden önce, diğerleri bunu diğer şekilde yapar. Üyeleri iki temel sınıftan gelmeleri halinde hangi yoldan sipariş veriyorsunuz? Ve bunun gibi. Derleyiciler bu sorular üzerinde büyük farklılıklar gösterir.
Ayrıca, sıfır / bir / sonsuzluk kuralı sayesinde, üyelerle iki sınıfınız olabileceğini söyledikten sonra, istediğiniz kadar diyebilirsiniz. Bu, bunun üstesinden gelmek için bir çok düzen kuralı eklemeyi gerektirir. Birden fazla mirasın nasıl çalıştığını, hangi sınıfların verilerini diğer sınıfların önüne koyduğunu vb. Söylemelisiniz. Çok az maddi kazanç için bu çok kural.
Sanal işlevleri ve varsayılan bir kurucu standart düzeni olmayan her şeyi yapamazsınız.
ve statik olmayan ilk veri üyesi temel sınıf türünde olamaz (bu, diğer adlandırma kurallarını bozabilir).
Gerçekten bununla konuşamam. Gerçekten anlamak için C ++ 'ın takma kuralları konusunda yeterince eğitimli değilim. Ancak temel elemanın, temel sınıfın kendisiyle aynı adresi paylaşmasıyla bir ilgisi vardır. Yani:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
Ve bu muhtemelen C ++ 'ın örtüşme kurallarına aykırıdır. Bir şekilde.
Ancak şunu düşünün: Bunu yapabilme yeteneği gerçekten ne kadar yararlı olabilir ? Yalnızca bir sınıf statik olmayan veri üyelerine sahip olabileceğinden, Derived
o sınıf olmalıdır (çünkü Base
bir üyesi olduğu için). Yani Base
gerekir (veri) boş. Ve eğer Base
boşsa ve bir temel sınıf ... neden bir veri üyesi var?
Yana Base
boştur, hiçbir devlet vardır. Bu nedenle, statik olmayan herhangi bir üye işlevi, this
işaretçilerine değil, parametrelerine göre yaptıklarını yapar .
Yine: büyük kayıp yok.
static_cast<Base*>(&d)
ve &d.b
aynı Base*
tür olmasına rağmen , farklı şeylere işaret ediyorlar ve böylece örtüşme kuralını kırıyorlar. Lütfen düzelt beni.
Derived
zaman bu sınıf olmalıdır?
Derived
İlk üyenin temel sınıfı olması için iki şeyi olmalıdır: bir temel sınıf ve bir üye . Ve hiyerarşideki yalnızca bir sınıfın üyeleri olabileceğinden (ve yine de standart mizanpajda olduğundan), temel sınıfının üye olamayacağı anlamına gelir.
C ++ 17 Uluslararası Standart final taslağını buradan indirin .
Agregalar
C ++ 17, agregaları ve toplu başlatmayı genişletir ve geliştirir. Standart kütüphane artık bir std::is_aggregate
tür özellik sınıfı da içermektedir . İşte bölüm 11.6.1.1 ve 11.6.1.2'deki resmi tanım (elli iç referanslar):
Toplama,
kullanıcı tarafından sağlanan, açık veya devralınan kurucuları
olmayan, özel veya korumalı statik olmayan veri üyeleri olmayan,
- sanal işlevleri
olmayan ve - sanal, özel veya korumalı temel sınıfları olmayan bir dizi veya sınıftır.
[Not: Toplam başlatma, korumalı ve özel temel sınıf üyelerine veya kurucularına erişmeye izin vermez. —End note]
Bir toplamanın öğeleri şunlardır:
- bir dizi için, artan alt simge sırasındaki dizi öğeleri veya
- bir sınıf için, bildirim düzenindeki doğrudan temel sınıflar, ardından doğrudan olmayan statik olmayan veri üyeleri anonim birliğin üyeleri, beyan düzeninde.
Ne değişti?
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;
};
Önemsiz Sınıflar
Önemsiz sınıfın tanımı, C ++ 14'te ele alınmayan çeşitli kusurları ele almak için C ++ 17'de yeniden düzenlendi. Değişiklikler teknik nitelikteydi. İşte 12.0.6'daki yeni tanım (dahili referanslar elid):
Önemsiz olarak kopyalanabilen bir sınıf bir sınıftır: - her kopya oluşturucu, taşıma kurucusu, kopya atama operatörü ve taşıma ataması operatörü silinmiş veya önemsizdir
- en az bir silinmemiş kopya oluşturucu, taşıma kurucusu, kopya atama operatörü, veya atama işlecini taşıyabilir ve
- önemsiz, silinmemiş bir yıkıcıya sahip olabilir.
Önemsiz sınıf, önemsiz şekilde kopyalanabilen ve tümü önemsiz veya silinmiş ve en az biri silinmeyen bir veya daha fazla varsayılan kurucuya sahip bir sınıftır. [Not: Özellikle önemsiz bir şekilde kopyalanabilen veya önemsiz bir sınıfın sanal işlevleri veya sanal temel sınıfları yoktur. - son not]
değişiklikler:
std::memcpy
. Bu semantik bir çelişkiydi, çünkü silinen tüm yapıcı / ödev operatörleri olarak tanımlandığında, sınıfın yaratıcısı sınıfın kopyalanamayacağını / taşınamayacağını açıkça hedeflemişti, ancak sınıf yine de önemsiz derecede kopyalanabilir bir sınıf tanımıyla tanıştı. Bu nedenle C ++ 17'de, önemsiz derecede kopyalanabilir sınıfın en az bir önemsiz, silinmemiş (her zaman kamuya açık olmasa da) kopyala / taşı kurucu / atama operatörü olması gerektiğini belirten yeni bir cümleye sahibiz. Bkz. N4148 , DR1734Standart Yerleşim Sınıfları
Standart mizanpajın tanımı da kusur raporlarını ele almak için elden geçirildi. Değişiklikler yine teknik nitelikteydi. İşte standart metin (12.0.7). Daha önce olduğu gibi, dahili referanslar seçilmiştir:
Bir S sınıfı, standart mizanpaj sınıfıdır:
- standart olmayan mizanpaj sınıfı (veya bu tür bir dizi) veya başvuru türünde statik olmayan veri üyeleri yoksa,
- sanal işlevleri ve sanal temel sınıfları yoksa,
- tüm statik olmayan veri üyeleri için aynı erişim denetimine
sahiptir , - standart olmayan düzen temel sınıflarına sahip değildir,
- herhangi bir türde en fazla bir temel sınıf alt nesneye
sahiptir , - tüm statik olmayan veri üyelerine ve bit alanlarına sahiptir. sınıf ve temel sınıfları ilk önce aynı sınıfta bildirilir ve
- temel sınıf olarak (aşağıda tanımlanan) tip M (S) kümesinin hiçbir öğesi yoktur. 108
M (X) aşağıdaki gibi tanımlanır:
- X, statik olmayan veri üyesi olmayan (muhtemelen devralınan) bir birleşim olmayan sınıf tipiyse, M (X) kümesi boştur.
- X, ilk statik olmayan veri üyesi X0 tipinde (adı geçen üye anonim bir birleşim olabilir) bir birleşim dışı sınıf tipiyse, M (X) kümesi X0 ve M (X0) öğelerinden oluşur.
- X bir birleşim tipiyse, M (X) kümesi tüm M (Ui) ve tüm Ui'leri içeren kümedir, burada her Ui X'in statik olmayan veri elemanının tipidir.
- X Xe eleman tipine sahip bir dizi tipidir, M (X) grubu Xe ve M (Xe) elemanlarından oluşur.
- X, sınıf olmayan, dizi olmayan bir türse, M (X) kümesi boştur.
[Not: M (X), standart mizanpaj sınıfında X'de sıfır ofsetinde olması garanti edilen, taban sınıfı olmayan tüm alt nesne türlerinin kümesidir. — Not notu]
[Örnek:
—End örnek]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) Bu, aynı sınıf türüne sahip ve aynı türetilmiş aynı nesneye ait iki alt nesnenin aynı adreste tahsis edilmemesini sağlar.
değişiklikler:
Not: C ++ standartlar komitesi, yeni dil yayınlanan C ++ 14 standardında olmasa da, yukarıdaki değişikliklerin hata raporlarına dayalı olarak C ++ 14'e uygulanmasını amaçlamıştır. C ++ 17 standardındadır.
Bu sorunun açık temasının ardından, agregaların anlamı ve kullanımı her standartla değişmeye devam ediyor. Ufukta birkaç önemli değişiklik var.
C ++ 17'de bu tür hala bir toplamdır:
struct X {
X() = delete;
};
Ve bu nedenle, X{}
hala derler çünkü bu, bir yapıcı çağrısı değil, toplam başlatmadır. Ayrıca bkz: Özel kurucu ne zaman özel kurucu değildir?
C ++ 20'de, kısıtlama aşağıdakilerden farklı olacak:
kullanıcı tarafından sağlanan
explicit
veya devralınan kurucu yok
için
kullanıcı tarafından bildirilmiş veya devralınan kurucu yok
Bu, C ++ 20 çalışma taslağına uyarlanmıştır . Ne X
burada ne de C
bağlantılı soruda C ++ 20'de toplanacaklar.
Bu ayrıca aşağıdaki örnekle yo-yo efekti sağlar:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
C ++ / 14 11, B
olduğu değil nedeniyle temel sınıf bir agrega böylece B{}
gerçekleştirir değer başlatma Aramaların B::B()
Aramaların A::A()
, erişilebilir olduğu bir noktada. Bu iyi biçimlenmişti.
C ++ 17'de, B
toplam sınıflandırmalara izin verilen temel sınıflara izin verildiğinden toplu hale geldi B{}
. Bu, kopya listesinin başlatılmasını bir A
from'den {}
, ancak B
erişilemediği bağlamın dışından gerektirir . C ++ 17'de, bu kötü biçimlidir ( auto x = B();
yine de iyi olurdu).
Şimdi C ++ 20'de, yukarıdaki kural değişikliği nedeniyle, bir B
kez daha (temel sınıf nedeniyle değil, ancak kullanıcı tarafından bildirilen varsayılan kurucu nedeniyle - varsayılan olsa da) bir toplam olmaktan çıkar. Bu yüzden B
yapıcısına geri dönüyoruz ve bu pasaj iyi biçimlenmiş hale geliyor.
Ortaya çıkan yaygın bir sorun, emplace()
agregalara sahip-stil yapıcıları kullanmak istemektir :
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
Bu işe yaramaz, çünkü geçerli olmayan emplace
başlatmayı etkili bir şekilde gerçekleştirmeye çalışacaktır X(1, 2)
. Tipik çözüm bir kurucu eklemektir X
, ancak bu öneriyle (şu anda Çekirdek yoluyla çalışmaktadır), agregalar doğru şeyi yapan ve düzenli kurucular gibi davranan sentezlenmiş kuruculara etkili bir şekilde sahip olacaktır. Yukarıdaki kod C ++ 20'deki gibi derlenecektir.
C ++ 17'de bu derlenmez:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
Kullanıcıların tüm toplu şablonlar için kendi kesinti kılavuzlarını yazmaları gerekir:
template <typename T> Point(T, T) -> Point<T>;
Ancak bu bir bakıma yapılacak "bariz şey" olduğundan ve temelde sadece ortak özellik olduğundan, dil bunu sizin için yapacaktır. Bu örnek C ++ 20'de derlenecektir (kullanıcı tarafından sağlanan kesinti kılavuzuna gerek olmadan).