このFAQは、アグリゲートとPODに関するもので、次の内容をカバーしています。
- 集合体とは何ですか?
- 何でPOD S(プレーン古いデータ)?
- それらはどのように関連していますか?
- どのようにそしてなぜ彼らは特別なのですか?
- C ++ 11の変更点
このFAQは、アグリゲートとPODに関するもので、次の内容をカバーしています。
回答:
この記事はかなり長いです。集約とPOD(プレーンな古いデータ)の両方について知りたい場合は、時間をかけて読んでください。集計だけに関心がある場合は、最初の部分のみを読んでください。PODのみに関心がある場合は、最初に集計の定義、影響、および例を読む必要があります。その後、PODにジャンプできますが、最初の部分全体を読むことをお勧めします。集約の概念は、PODを定義するために不可欠です。エラー(文法、文体、書式、構文などを含む)が見つかった場合は、コメントを残してください。編集します。
この回答はC ++ 03に適用されます。他のC ++標準については、以下を参照してください。
C ++標準からの正式な定義(C ++ 03 8.5.1§1):
集約は、ユーザー宣言コンストラクター(12.1)、プライベートまたは保護された非静的データメンバー(句11)、基本クラス(句10)、仮想関数(10.3)のない配列またはクラス(句9)です。 )。
それでは、この定義を解析してみましょう。まず、どの配列も集合体です。次の場合、クラスは集合体になることもできます。構造体や共用体については何も言われていません。それらは集合体になりませんか?はい、できます。C ++では、この用語class
はすべてのクラス、構造体、および共用体を指します。したがって、クラス(または構造体、共用体)は、上記の定義の基準を満たす場合にのみ、集合体になります。これらの基準は何を意味しますか?
これは、集約クラスがコンストラクターを持つことができないことを意味しません。実際には、ユーザーによって明示的にではなく、コンパイラーによって暗黙的に宣言されている限り、デフォルトのコンストラクターやコピーコンストラクターを持つことができます。
プライベートまたは保護された非静的データメンバーはありません。プライベートクラスおよび保護されたメンバー関数(コンストラクターを除く)だけでなく、プライベートクラスまたは保護された静的データメンバーおよびメンバー関数も、必要に応じていくつでも持つことができ、集約クラスのルールに違反しません。
集約クラスには、ユーザー宣言/ユーザー定義のコピー割り当て演算子やデストラクタを含めることができます
配列は、非集合クラス型の配列であっても集合です。
次に、いくつかの例を見てみましょう。
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
};
あなたはアイデアを得ます。次に、集計が特別な方法を見てみましょう。非集計クラスとは異なり、中括弧で初期化できます{}
。この初期化構文は配列でよく知られていますが、これらは集約であることを学びました。それでは、それらから始めましょう。
Type array_name[n] = {a1, a2, …, am};
if(m == n)配列の
i番目の要素はa iで初期化され
ますelse if(m <n)
配列の最初のm要素は 1、a 2、…、a mおよびその他のn - m
要素で初期化されます可能であれば、値を初期化します(用語の説明については以下を参照してください)
else if(m> n)
コンパイラはエラー
else を発行します(これは、nがのようにまったく指定されていない場合int a[] = {1, 2, 3};
)
のサイズ配列(n)はmに等しいと仮定されるため、int a[] = {1, 2, 3};
と同等です。int a[3] = {1, 2, 3};
スカラー型のオブジェクト(場合bool
、int
、char
、double
、ポインタ、等)がある値に初期化それがで初期化されている手段0
(そのタイプのfalse
ためbool
、0.0
用double
等)。ユーザー宣言のデフォルトコンストラクターを持つクラスタイプのオブジェクトが値で初期化されると、そのデフォルトコンストラクターが呼び出されます。デフォルトのコンストラクタが暗黙的に定義されている場合、すべての非静的メンバーは再帰的に値で初期化されます。この定義は不正確で少し不正確ですが、基本的な考え方がわかるはずです。参照を値で初期化することはできません。非適切なクラスの値の初期化は、たとえば、クラスに適切なデフォルトのコンストラクタがない場合に失敗する可能性があります。
配列の初期化の例:
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
}
ここで、中括弧で集約クラスを初期化する方法を見てみましょう。ほぼ同じです。配列要素の代わりに、クラス定義での出現順に非静的データメンバーを初期化します(定義によりすべてパブリックです)。メンバーよりも少ない初期化子がある場合、残りは値で初期化されます。明示的に初期化されていないメンバーの1つを値初期化することが不可能な場合は、コンパイル時エラーが発生します。イニシャライザが必要以上に多い場合、コンパイル時エラーも発生します。
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}};
上記の例でy.c
用いて初期化される'a'
、y.x.i1
と10
、y.x.i2
と20
、y.i[0]
と20
、y.i[1]
と30
とy.f
値初期化、で初期化されます0.0
。保護された静的メンバーd
はなので、まったく初期化されませんstatic
。
集計ユニオンは、最初のメンバーのみを中括弧で初期化できるという点で異なります。ユニオンの使用を検討するのに十分な高度なC ++を使用している場合(その使用は非常に危険であり、慎重に検討する必要があります)、標準でユニオンのルールを調べることができると思います:)。
集計の何が特別なのかがわかったところで、クラスの制限を理解してみましょう。つまり、なぜ彼らがそこにいるのか。中括弧を使用したメンバーごとの初期化は、クラスがそのメンバーの合計に過ぎないことを意味することを理解する必要があります。ユーザー定義コンストラクタが存在する場合は、ユーザーがメンバーを初期化するために追加の作業を行う必要があるため、ブレースの初期化は正しくありません。仮想関数が存在する場合、これは、このクラスのオブジェクトが(ほとんどの実装で)コンストラクターで設定されるクラスのいわゆるvtableへのポインターを持っていることを意味するため、ブレース初期化では不十分です。残りの制限は、演習と同様の方法で理解できます。
集計については十分です。これで、より厳密なタイプのセット、つまりPODを定義できます
C ++標準からの正式な定義(C ++ 03 9§4):
POD構造体は、非POD構造体、非PODユニオン(またはそのような型の配列)、または参照のタイプの非静的データメンバーがなく、ユーザー定義のコピー割り当て演算子とユーザー定義のデストラクタ。同様に、PODユニオンは、非POD構造体、非PODユニオン(またはそのようなタイプの配列)、または参照のタイプの非静的データメンバーがなく、ユーザー定義のコピー代入演算子がない集約ユニオンです。ユーザー定義のデストラクタはありません。PODクラスは、POD構造体またはPODユニオンのいずれかであるクラスです。
これは解析が難しいですね。:)ユニオンを(上記と同じ理由で)除外し、少し明確に言い換えましょう。
集約クラスは、ユーザー定義のコピー割り当て演算子とデストラクタがなく、非静的メンバーが非PODクラス、非PODの配列、または参照でない場合、PODと呼ばれます。
この定義は何を意味しますか?(私はPODがPlain Old Dataの略であることを言及しましたか?)
例:
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クラス、PODユニオン、スカラー型、およびそのような型の配列は、まとめてPODタイプと呼ばれます。
PODは多くの点で特別です。いくつか例を挙げます。
PODクラスはCの構造体に最も近いものです。それらとは異なり、PODはメンバー関数と任意の静的メンバーを持つことができますが、これら2つのどちらもオブジェクトのメモリレイアウトを変更しません。したがって、Cや.NETからも使用できる多かれ少なかれ移植可能な動的ライブラリを作成したい場合は、エクスポートされたすべての関数がPOD型のパラメータのみを取得して返すようにする必要があります。
非PODクラスタイプのオブジェクトの存続期間は、コンストラクタが終了したときに始まり、デストラクタが終了したときに終了します。PODクラスの場合、存続期間はオブジェクトのストレージが占有されると始まり、そのストレージが解放または再利用されると終了します。
PODタイプのオブジェクトの場合、標準によって、memcpy
オブジェクトのコンテンツをcharまたはunsigned charの配列にしてからコンテンツをオブジェクトにmemcpy
戻すと、オブジェクトは元の値を保持することが保証されます。非PODタイプのオブジェクトについては、そのような保証がないことに注意してください。また、を使用してPODオブジェクトを安全にコピーできますmemcpy
。次の例では、Tが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
gotoステートメント。ご存知かもしれませんが、変数がまだスコープ内にないポイントから、すでにスコープ内にあるポイントにgotoを介してジャンプすることは違法です(コンパイラーはエラーを発行する必要があります)。この制限は、変数が非PODタイプの場合にのみ適用されます。次の例ではf()
は、g()
整形式ですが、整形式です。Microsoftのコンパイラはこのルールに寛大すぎることに注意してください。両方の場合に警告を発行するだけです。
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;
}
PODオブジェクトの先頭にはパディングがないことが保証されています。つまり、PODクラスAの最初のメンバーがタイプTの場合、安全にreinterpret_cast
からA*
のT*
第1部材およびその逆へのポインタを取得します。
リストはどんどん続きます…
ご覧のように、多くの言語機能は動作が異なるため、PODが正確に何であるかを理解することが重要です。
private:
適宜):struct A { int const a; };
その後A()
も、整形式でA
のデフォルトコンストラクタの定義が病気に形成されるだろう。
集計の標準的な定義は少し変更されましたが、それでもほとんど同じです。
集約は、ユーザー提供のコンストラクター(12.1)、非静的データメンバーのブレースまたはイコライザー(9.2)、プライベートまたは保護された非静的データメンバー(条項11)、基本クラス(条項10)、仮想関数(10.3)なし。
何が変わったの?
以前は、ユーザー定義のコンストラクターを集約に含めることはできませんでしたが、ユーザー提供のコンストラクターを含めることはできなくなりました。違いはありますか?はい、あります。これで、コンストラクタを宣言してデフォルトにすることができるからです。
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
最初の宣言でデフォルトになっているコンストラクター(または任意の特別なメンバー関数)がユーザー提供ではないため、これは依然として集約です。
現在、集約では、非静的データメンバーのブレースまたはイニシャライザを使用できません。これは何を意味するのでしょうか?これは、この新しい標準を使用すると、次のようにクラスのメンバーを直接初期化できるからです。
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
この機能を使用すると、基本的に独自のデフォルトコンストラクターを提供することと同じになるため、クラスは集合体ではなくなります。
つまり、集約とは何もほとんど変わりませんでした。新しい機能に合わせて、それはまだ基本的な考え方は同じです。
PODには多くの変更が加えられました。この新しい規格では、PODに関するこれまでの多くのルールが緩和され、規格での定義の提供方法が根本的に変更されました。
PODの考え方は、基本的に2つの異なるプロパティをキャプチャすることです。
このため、定義は2つの異なる概念に分割されています。それは、PODよりも有用であるため、トリビアルクラスと標準レイアウトクラスです。現在、標準はPODという用語をほとんど使用せず、より具体的で平凡で標準的なレイアウトを優先しています。概念がます。
新しい定義は基本的に、PODは簡単で標準的なレイアウトを持つクラスであり、このプロパティはすべての非静的データメンバーに対して再帰的に保持する必要があることを示しています。
POD構造体は、自明なクラスであり標準レイアウトクラスでもある非ユニオンクラスであり、非POD構造体、非PODユニオン(またはそのようなタイプの配列)タイプの非静的データメンバーはありません。同様に、POD共用体は、自明なクラスと標準レイアウトクラスの両方である共用体であり、非POD構造体、非POD共用体(またはそのような型の配列)型の非静的データメンバーはありません。PODクラスは、POD構造体またはPODユニオンであるクラスです。
これらの2つのプロパティを個別に詳しく見ていきましょう。
トリビアルは、上記の最初のプロパティです。トリビアルクラスは、静的な初期化をサポートしています。クラスが自明にコピー可能(自明なクラスのスーパーセット)である場合、その表現を次のように場所にコピーすることは問題ありません。memcpy
、結果が同じになることを期待できます。
標準では、次のように簡単なクラスを定義しています。
自明にコピー可能なクラスは、次のようなクラスです。
—重要なコピーコンストラクターがない(12.8)。
—重要な移動コンストラクタはありません(12.8)。
—重要なコピー割り当て演算子がない(13.5.3、12.8)、
—重要な移動代入演算子(13.5.3、12.8)がない、および
—ささいなデストラクタがあります(12.4)。
自明なクラスとは、自明なデフォルトコンストラクター(12.1)を持ち、自明にコピー可能なクラスです。
[ 注:特に、自明にコピー可能なクラスまたは自明なクラスには、仮想関数または仮想基本クラスがありません。—エンドノート ]
では、これらすべての些細なこととそうでないことは何ですか?
クラスXのコピー/移動コンストラクターは、ユーザー指定ではなく、
—クラスXには仮想関数(10.3)および仮想基本クラス(10.1)がありません。
—各直接基本クラスサブオブジェクトをコピー/移動するために選択されたコンストラクタは簡単です。
—クラス型(またはその配列)であるXの非静的データメンバーごとに、そのメンバーをコピー/移動するために選択されたコンストラクターは簡単です。
それ以外の場合、コピー/移動コンストラクタは重要です。
基本的に、これは、ユーザーが指定せず、クラスに仮想的なものがない場合、コピーまたは移動コンストラクターは取るに足らないものであり、このプロパティはクラスのすべてのメンバーと基本クラスに対して再帰的に保持されることを意味します。
自明なコピー/移動代入演算子の定義は非常によく似ており、単に「コンストラクタ」という単語を「代入演算子」に置き換えます。
自明なデストラクタにも同様の定義があり、仮想にはできないという制約が追加されています。
さらに、単純なデフォルトコンストラクターにも同様のルールが存在します。加えて、クラスに上記のbrace-or-equal-initializersを持つ非静的データメンバーがある場合、デフォルトコンストラクターは重要です。
以下に、すべてをクリアする例をいくつか示します。
// 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
};
標準レイアウトは2番目のプロパティです。標準は、これらが他の言語との通信に役立つと述べています。これは、標準レイアウトクラスが、同等のC構造体または共用体と同じメモリレイアウトを持っているためです。
これは、メンバーとすべての基本クラスに対して再帰的に保持する必要がある別のプロパティです。そしていつものように、仮想関数や仮想基本クラスは許可されていません。レイアウトがCと互換性がなくなります。
ここでの緩和されたルールは、標準レイアウトクラスは、同じアクセス制御を持つすべての非静的データメンバーを持たなければならないということです。以前はすべてパブリックにする必要がありましたが、すべてプライベートまたはすべて保護されている限り、プライベートまたは保護にすることができます。
継承を使用する場合、1つだけ継承ツリー全体でクラスが非静的データメンバーを持つことができ、最初の非静的データメンバーを基本クラスタイプにすることはできません(エイリアスルールに違反する可能性があります)。それ以外の場合、標準ではありません-レイアウトクラス。
これは標準テキストでの定義の仕方です:
標準レイアウトクラスは、次のようなクラスです。
—非標準レイアウトクラス(またはそのようなタイプの配列)または参照のタイプの非静的データメンバーがありません。
—仮想関数(10.3)も仮想基本クラス(10.1)もありません。
—すべての非静的データメンバーに対して同じアクセス制御(条項11)があります。
—非標準レイアウトの基本クラスはありません。
—ほとんどの派生クラスに非静的データメンバーがなく、非静的データメンバーを持つ最大1つの基本クラスがあるか、または非静的データメンバーを持つ基本クラスがない。
—最初の非静的データメンバーと同じタイプの基本クラスがありません。
標準レイアウト構造体は、クラスキー構造体またはクラスキークラスで定義された標準レイアウトクラスです。
標準レイアウト共用体は、クラスキー共用体で定義された標準レイアウトクラスです。
[ 注:標準レイアウトクラスは、他のプログラミング言語で記述されたコードとの通信に役立ちます。それらのレイアウトは9.2で指定されています。—エンドノート ]
そして、いくつかの例を見てみましょう。
// 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
これらの新しいルールにより、より多くのタイプがPODになる可能性があります。また、タイプがPODでない場合でも、いくつかのPODプロパティを個別に利用できます(それが単純なレイアウトまたは標準的なレイアウトの1つだけの場合)。
標準ライブラリには、ヘッダーでこれらのプロパティをテストする特性があります<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;
ドラフトC ++ 14標準を参照できますを参照できます。
これはセクション8.5.1
Aggregatesでカバーされており、次の定義が得られます。
集約は、ユーザー提供のコンストラクター(12.1)、プライベートまたは保護された非静的データメンバー(条項11)、基本クラス(条項10)、および仮想関数(10.3)がない配列またはクラス(条項9)です。 )。
唯一の変更は、クラス内のメンバー初期化子を追加しても、クラスが非集合にならないことです。したがって、C ++ 11の次の例は、メンバーのインプレース初期化子を持つクラスの初期化を集約します。
struct A
{
int a = 3;
int b = 3;
};
C ++ 11では集計ではありませんでしたが、C ++ 14では集計です。この変更は、N3605:メンバー初期化子と集計でカバーされています。
Bjarne StroustrupとRichard Smithは、集約初期化とメンバー初期化子が一緒に機能しないという問題を提起しました。このペーパーでは、集計にメンバー初期化子を含めることができないという制限を取り除くスミスの提案された表現を採用することで、問題を修正することを提案しています。
POD(プレーン・オールド・データ)構造体の定義は、9
クラスのセクションで説明されています。
POD構造体110は、自明なクラスであり標準レイアウトクラスでもある非ユニオンクラスであり、非POD構造体、非PODユニオン(またはそのようなタイプの配列)タイプの非静的データメンバーを持ちません。同様に、PODユニオンは、自明なクラスと標準レイアウトクラスの両方であるユニオンであり、非POD構造体、非PODユニオン(またはそのようなタイプの配列)型の非静的データメンバーを持ちません。PODクラスは、POD構造体またはPODユニオンであるクラスです。
これはC ++ 11と同じ表現です。
コメントで述べたように、ポッドは標準レイアウトの定義に依存しており、C ++ 14では変更されましたが、これは、事後にC ++ 14に適用された欠陥レポートによるものでした。
3つのDRがありました。
したがって、標準レイアウトはこのPre C ++ 14からのものです。
標準レイアウトクラスは、次のようなクラスです。
- (7.1)非標準レイアウトクラス(またはそのようなタイプの配列)または参照のタイプの非静的データメンバーがない。
- (7.2)仮想関数([class.virtual])および仮想基本クラス([class.mi])がない。
- (7.3)すべての非静的データメンバーに対して同じアクセス制御(節[class.access])がある
- (7.4)非標準レイアウトの基本クラスはありません。
- (7.5)最も派生したクラスに非静的データメンバーがなく、非静的データメンバーを持つ最大1つの基本クラスがあるか、非静的データメンバーを持つ基本クラスがない、および
- (7.6)には、最初の非静的データメンバーと同じ型の基本クラスはありません。
次の場合、クラスSは標準レイアウトクラスです。
- (3.1)非標準レイアウトクラス(またはそのようなタイプの配列)または参照のタイプの非静的データメンバーがない
- (3.2)仮想関数も仮想基底クラスもありません。
- (3.3)すべての非静的データメンバーに対して同じアクセス制御があり、
- (3.4)非標準レイアウトの基本クラスはありません。
- (3.5)任意のタイプの最大1つの基本クラスサブオブジェクトを持ち、
- (3.6)同じ静的クラスで最初に宣言されたクラスとその基本クラスにすべての非静的データメンバーとビットフィールドがある
- (3.7)には、基本クラスとしての型のセットM(S)の要素はありません。ここで、任意の型Xについて、M(X)は次のように定義されます。104[注:M(X)は、 Xのゼロオフセットにある可能性があるすべての非基本クラスのサブオブジェクト—終了ノート]
- (3.7.1)Xが、(おそらく継承された)非静的データメンバーのない共用体クラス型である場合、セットM(X)は空です。
- (3.7.2)Xがサイズが0であるか、Xの最初の非静的データメンバーであるX0型の非静的データメンバーを持つ非共用クラスタイプである場合(このメンバーは匿名共用体である可能性があります) )、セットM(X)はX0とM(X0)の要素で構成されます。
- (3.7.3)Xが共用体型の場合、セットM(X)はすべてのM(Ui)とすべてのUiを含むセットの共用体であり、各UiはXのi番目の非静的データメンバーの型です。
- (3.7.4)Xが要素タイプXeの配列タイプの場合、セットM(X)はXeとM(Xe)の要素で構成されます。
- (3.7.5)Xが非クラス、非配列型の場合、セットM(X)は空です。
次のルールについて詳しく説明してください。
私が試してみます:
a)標準レイアウトクラスには、同じアクセス制御を持つすべての非静的データメンバーが必要です
それは簡単です:すべての非静的データメンバをしなければならないすべてのことpublic
、private
またはprotected
。あなたはいくつかpublic
といくつかを持つことはできませんprivate
。
それらの推論は、「標準レイアウト」と「非標準レイアウト」をまったく区別するための推論になります。つまり、コンパイラに、物事をメモリに配置する方法を自由に選択できるようにします。それは、vtableポインタだけではありません。
彼らが98年にC ++を標準化したとき、彼らは基本的に人々がそれを実装する方法を予測しなければなりませんでした。C ++のさまざまなフレーバーを使用した実装の経験はかなりありましたが、確信を持っていませんでした。そこで彼らは用心することに決めました:コンパイラにできるだけ多くの自由を与えてください。
そのため、C ++ 98でのPODの定義は非常に厳密です。これにより、C ++コンパイラは、ほとんどのクラスのメンバーレイアウトに大きな自由度を与えました。基本的に、PODタイプは特別なケースであることが意図されており、あなたが理由のために具体的に書いたものです。
C ++ 11が開発されていたとき、彼らはコンパイラに関してより多くの経験を持っていました。そして彼らは、C ++コンパイラの作成者が本当に怠惰であることを理解しました。彼らはすべて、この自由を持っていたが、彼らはなかったんそれで何かを。
標準レイアウトの規則は、多かれ少なかれ一般的な慣行を体系化したものです。ほとんどのコンパイラーは、それらを実装するために何かを変更する必要はほとんどありませんでした(対応する型の特性のいくつかのもの以外)。
さて、それがpublic
/になるとprivate
、状況は異なります。どのメンバーがpublic
対であるかをprivate
実際に並べ替える自由は、特にビルドのデバッグにおいて、コンパイラーにとって重要な場合があります。また、標準レイアウトのポイントは他の言語との互換性があることなので、デバッグ版とリリース版でレイアウトを変えることはできません。
そして、それがユーザーを本当に傷つけないという事実があります。カプセル化されたクラスを作成している場合、すべてのデータメンバーがいずれにしても、確率は高いprivate
です。通常、完全にカプセル化された型のパブリックデータメンバーは公開しません。したがって、これは、それを実行したい、その部門を実行したい少数のユーザーにとってのみ問題になります。
したがって、大きな損失ではありません。
b)継承ツリー全体で1つのクラスのみが非静的データメンバーを持つことができます。
この理由は、標準レイアウトを再び標準化した理由に戻ります。これは一般的な方法です。
実際に物を格納する継承ツリーの2つのメンバーを持つことに関しては、一般的な方法はありません。派生クラスの前に基本クラスを配置するものもあれば、逆の方法で配置するものもあります。メンバーが2つの基本クラスに属している場合、どのようにメンバーを注文しますか?等々。コンパイラはこれらの質問で大きく異なります。
また、zero / one / infinityルールのおかげで、メンバーを持つ2つのクラスを持つことができると言うと、必要なだけいくつでも言うことができます。これには、これを処理する方法に関する多くのレイアウト規則を追加する必要があります。複数の継承がどのように機能するか、どのクラスが他のクラスの前にデータを配置するかなどを言わなければなりません。これは多くのルールであり、実質的な利益はほとんどありません。
仮想関数とデフォルトのコンストラクター標準レイアウトを持たないものすべてを作成することはできません。
また、最初の非静的データメンバーを基本クラス型にすることはできません(これにより、エイリアスルールが壊れる可能性があります)。
これは本当に話せません。C ++のエイリアシングルールについて十分に理解していないため、実際にそれを理解できません。しかし、それは基本メンバーが基本クラス自体と同じアドレスを共有するという事実と関係があります。あれは:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
そして、それはおそらくC ++のエイリアシング規則に反しています。何らかの方法で。
ただし、これを検討してください。これを実行する機能を実際に使用すると、どれほど役立つでしょうか。非静的データメンバーを持つことができるクラスは1つだけなので、Derived
そのクラスでなければなりません(Base
メンバーとしてがあるため)。したがって、(データの)空でBase
なければなりません。そして、もしBase
空の場合、基本クラスと同様に ...なぜそれのデータメンバーがあるのですか?
Base
は空なので、状態はありません。したがって、非静的メンバー関数は、this
ポインターではなく、パラメーターに基づいて実行することを実行します。
繰り返しますが、大きな損失はありません。
static_cast<Base*>(&d)
と&d.b
同じBase*
タイプ、彼らはこのようにエイリアシング規則を破る異なるものを指します。訂正してください。
Derived
そのクラスでなければならないのですか?
Derived
の最初のメンバーがその基本クラスになるためには、基本クラスとメンバーの 2つの要素が必要です。また、階層内の1つのクラスだけがメンバーを持つことができる(それでも標準レイアウトである)ため、これはその基本クラスがメンバーを持つことができないことを意味します。
C ++ 17国際標準の最終ドラフトをこちらからダウンロードしてください。
集計
C ++ 17は、集約および集約初期化を拡張および拡張します。標準ライブラリには、std::is_aggregate
タイプ特性クラスも含まれています。セクション11.6.1.1および11.6.1.2からの正式な定義は次のとおりです(内部参照は省略されています)。
集約は、配列またはクラスであり
、ユーザー提供、明示、または継承コンストラクター
なし、プライベートまたは保護された非静的データメンバー
なし、仮想関数なし、および
仮想、プライベート、または保護された基本クラスなし。
[注:集計の初期化では、保護およびプライベートの基本クラスのメンバーまたはコンストラクターへのアクセスは許可されていません。—エンドノート]
— 集合体の要素は次のとおりです配列の場合、添え字の昇順の配列要素、または
—クラスの場合、宣言の順序の直接基本クラス、その後にない非静的データメンバー宣言の順序での匿名組合のメンバー。
何が変わったの?
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;
};
ささいなクラス
トリビアルクラスの定義がC ++ 17で作り直され、C ++ 14では対処されなかったいくつかの不具合に対処しました。変更は本質的に技術的なものでした。これは、12.0.6での新しい定義です(内部参照は省略されています)。
自明なコピー可能なクラスとは、次のクラスです。—
各コピーコンストラクター、移動コンストラクター、コピー割り当て演算子、および移動割り当て演算子が削除されるか、または自明です。—削除され
ていないコピーコンストラクター、移動コンストラクター、コピー割り当て演算子が少なくとも1つあります。または代入演算子を移動し、
—ささいな、削除されていないデストラクタがあります。
トリビアルクラスは、トリビアルコピーが可能なクラスであり、1つ以上のデフォルトコンストラクターがあり、そのすべてがトリビアルまたは削除されており、少なくとも1つは削除されていません。[注:特に、自明にコピー可能なクラスまたは自明なクラスには、仮想関数または仮想基本クラスがありません。—エンドノート]
変更:
std::memcpy
ます。すべてのコンストラクター/割り当て演算子を削除済みとして定義することにより、クラスの作成者はクラスをコピー/移動できないことを明確に意図していましたが、クラスはまだ自明なコピー可能なクラスの定義を満たしていました。したがって、C ++ 17には、自明にコピー可能なクラスに、少なくとも1つの自明で削除されていない(必ずしもパブリックにアクセスできるとは限りません)コピー/移動コンストラクター/割り当て演算子が必要であるという新しい句があります。N4148、DR1734を参照標準レイアウトクラス
標準レイアウトの定義も、欠陥レポートに対処するために再作成されました。再び、変更は本質的に技術的なものでした。これは標準(12.0.7)からのテキストです。以前と同様に、内部参照は省略されています。
クラスSは、次の場合に標準レイアウトクラスです
。—非標準レイアウトクラス(またはそのようなタイプの配列)または参照の非静的データメンバー
がない場合—仮想関数および仮想基本クラスがない場合
—すべての非静的データメンバーに対して同じアクセス制御があります
—非標準レイアウトの基本クラスは
ありません
— — 任意のタイプの最大1つの基本クラスサブオブジェクトがあります—すべての非静的データメンバーとビットフィールドがあります同じクラスで最初に宣言されたクラスとその基本クラス、および
— 基本クラスとしてのタイプ(以下で定義)のセットM(S)の要素がありません。108—
(X)は次のように定義されます。
— Xが非ユニオンクラスタイプであり、継承されていない可能性のある非静的データメンバーがない場合、セットM(X)は空です。
— Xが非ユニオンクラス型であり、その最初の非静的データメンバーの型がX0である場合(このメンバーは匿名ユニオンである可能性があります)、セットM(X)はX0とM(X0)の要素で構成されます。
— Xがユニオンタイプの場合、セットM(X)はすべてのM(Ui)とすべてのUiを含むセットのユニオンであり、各UiはXのi番目の非静的データメンバーのタイプです。—
Xの場合要素タイプXeの配列タイプで、セットM(X)はXeとM(Xe)の要素で構成されます。
— Xがクラスでも配列でもないタイプの場合、セットM(X)は空です。
[注:M(X)は、標準レイアウトクラスでXのゼロオフセットにあることが保証されているすべての非基本クラスサブオブジェクトのタイプのセットです。—end note]
[例:
—end example]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)これにより、同じクラスタイプを持ち、最も派生した同じオブジェクトに属する2つのサブオブジェクトが同じアドレスに割り当てられないことが保証されます。
変更:
注:新しい言語は公開されたC ++ 14標準には含まれていませんが、C ++標準委員会は、欠陥レポートに基づく上記の変更をC ++ 14に適用することを意図していました。これはC ++ 17標準に含まれています。
この質問の残りの明確なテーマに従って、集計の意味と使用法はすべての標準で変わり続けています。地平線にはいくつかの重要な変更があります。
C ++ 17では、この型はまだ集合体です:
struct X {
X() = delete;
};
そして、それは、それX{}
が集合体の初期化であり、コンストラクターの呼び出しではないため、依然としてコンパイルされます。参照:プライベートコンストラクターがプライベートコンストラクターではない場合
C ++ 20では、制限は次のものを要求するように変更されます。
ユーザー提供、
explicit
継承、継承のコンストラクタはありません
に
ユーザー宣言または継承コンストラクターなし
これは、C ++ 20ワーキングドラフトに採用されました。C ++ 20では、リンクされた質問のX
hereもthe C
も集約ではありません。
これは、次の例のヨーヨー効果ももたらします。
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
++ 11月14日Cにおいて、B
あったしないように、基本クラスの集合体によりB{}
実行値初期化れるコールB::B()
のコールA::A()
がアクセス可能である点で、。これは整形式でした。
C ++ 17では、B
基本クラスが許可されたために集約になり、集約のB{}
初期化が行われました。これには、A
からのコピーリスト初期化が必要ですが、アクセスできないの{}
コンテキストの外側からB
です。C ++ 17では、これは形式auto x = B();
が正しくありません(ただし問題ありません)。
現在のC ++ 20では、上記のルールの変更により、B
(基本クラスのためではなく、ユーザーが宣言したデフォルトコンストラクターのために(デフォルトであっても)集合体でなくなりました)。これでB
、のコンストラクターに戻り、このスニペットは整形式になります。
発生する一般的な問題はemplace()
、集計で-スタイルコンストラクターを使用することです:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
は有効ではないemplace
初期化を効果的に実行しようとするためX(1, 2)
、これは機能しません。典型的な解決策は、コンストラクターをに追加することX
ですが、この提案(現在はCoreを介して機能しています)を使用すると、集約は、正しいことを行う合成コンストラクターを効果的に持ち、通常のコンストラクターのように動作します。上記のコードは、C ++ 20でそのままコンパイルされます。
C ++ 17では、これはコンパイルされません。
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
ユーザーは、すべての集計テンプレートに対して独自の控除ガイドを作成する必要があります。
template <typename T> Point(T, T) -> Point<T>;
しかし、これはある意味では「明白なこと」であり、基本的には単なる定型文であるため、言語がこれを行います。この例はC ++ 20でコンパイルされます(ユーザー提供の控除ガイドは必要ありません)。