web-dev-qa-db-ja.com

アグリゲートとPODとは何ですか、またそれらは特別なのはなぜですか。

この FAQ は集約とPODに関するもので、次の資料を扱います。

  • 集合体とは何ですか?
  • PODsとは何ですか?
  • それらはどのように関連していますか?
  • どのようにそしてなぜそれらは特別なのですか?
  • C++ 11の変更点
503
Armen Tsirunyan

読み方:

この記事はかなり長いです。集計とPOD(Plain Old Data)の両方について知りたい場合は、時間をかけて読んでください。集計のみに関心がある場合は、最初の部分のみを読んでください。 PODのみに関心がある場合は、まず集約の定義、意味、および例を読む必要があります。次に、PODにジャンプできますが、最初の部分を読むことをお勧めしますその全体。集計の概念は、PODを定義するために不可欠です。エラー(文法、文体、書式、構文などを含む小さなものでも)を見つけた場合は、コメントを残してください、私は編集します。

この回答はC++ 03に適用されます。他のC++標準については、以下を参照してください。

集約とは何か、なぜ特別なのか

C++標準の正式な定義(C++ 03 8.5.1§1

集合体は、ユーザー宣言コンストラクター(12.1)、プライベートまたは保護された非静的データメンバー(11節)、基本クラス(10節)、および仮想関数(10.3)を持たない配列またはクラス(9節)です。 )。

それでは、この定義を解析しましょう。まず、配列は集合体です。クラスは、…待機する場合、集合体になることもできます。構造体や共用体については何も言われていませんが、それらは集合体ではありませんか?はい、できます。 C++では、classという用語は、すべてのクラス、構造体、および共用体を指します。したがって、クラス(または構造体、またはユニオン)は、上記の定義の基準を満たす場合にのみ集約されます。これらの基準は何を意味していますか?

  • これは、集約クラスがコンストラクターを持つことができないことを意味するものではなく、実際には、ユーザーによって明示的にではなく、コンパイラーによって暗黙的に宣言されている限り、デフォルトコンストラクターまたはコピーコンストラクターを持つことができます

  • プライベートまたは保護なし非静的データメンバープライベートおよび保護されたメンバー関数(コンストラクターは除く)とプライベートまたは保護されたstaticデータメンバーとメンバー関数は好きなように、集計クラスのルールに違反しません

  • 集約クラスには、ユーザー宣言/ユーザー定義のコピー割り当て演算子またはデストラクタを含めることができます

  • 配列は、非集約クラス型の配列であっても集約です。

次に、いくつかの例を見てみましょう。

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番目 配列の要素は
else if(m <n)
配列の最初のm個の要素は、12、…、am 可能であれば、他のn - m要素はvalue-initializedです(用語の説明については以下を参照してください)
else if(m> n)
コンパイラーはエラーを発行します
else(nの場合int a[] = {1, 2, 3};)のように指定されていません
配列のサイズ(n)はmと等しいと想定されるため、int a[] = {1, 2, 3};int a[3] = {1, 2, 3};と同等です

スカラー型のオブジェクト(boolintchardouble、ポインターなど)がvalue-initializedそのタイプの0で初期化されることを意味します(falseの場合はbooldoubleの場合は0.0など)。ユーザーが宣言したデフォルトコンストラクタを持つクラスタイプのオブジェクトが値で初期化されると、そのデフォルトコンストラクタが呼び出されます。デフォルトのコンストラクターが暗黙的に定義されている場合、すべての非静的メンバーは再帰的に値で初期化されます。この定義は不正確で少し不正確ですが、基本的な考え方がわかるはずです。参照は値で初期化できません。たとえば、クラスに適切なデフォルトコンストラクターがない場合、非集約クラスの値の初期化は失敗する可能性があります。

配列の初期化の例:

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.i110で初期化され、y.x.i220で初期化され、y.i[0]20で、y.i[1]30で、y.fが初期化されます。保護された静的メンバーdstaticであるため、まったく初期化されません。

集約共用体は、最初のメンバーのみを中括弧で初期化できるという点で異なります。ユニオンの使用を検討するほどC++が十分に進んでいる場合(その使用は非常に危険であり、慎重に検討する必要があります)、標準のユニオンのルールを自分で調べることができると思います:)。

集計の特別なところがわかったので、クラスの制限を理解してみましょう。それが彼らがそこにいる理由です。中括弧でのメンバーごとの初期化は、クラスがそのメンバーの合計にすぎないことを意味することを理解する必要があります。ユーザー定義のコンストラクターが存在する場合、ユーザーがメンバーを初期化するために追加の作業を行う必要があるため、ブレースの初期化は正しくありません。仮想関数が存在する場合、このクラスのオブジェクトには(ほとんどの実装で)クラスのいわゆるvtableへのポインターがあり、コンストラクターで設定されているため、ブレースの初期化は不十分です。演習と同様の方法で、残りの制限を把握できます:)。

集計については十分です。これで、PODをより厳密に定義することができます。

PODとは何か、なぜ特別なのか

C++標準の正式な定義(C++ 03 9§4

POD構造体は、非POD構造体、非POD共用体(またはそのような型の配列)、または参照型の非静的データメンバーを持たない集約クラスであり、ユーザー定義のコピー割り当て演算子とユーザー定義のデストラクタ。同様に、PODユニオンは、非POD構造型、非PODユニオン(またはそのようなタイプの配列)、または参照型の非静的データメンバーを持たない集約ユニオンであり、ユーザー定義のコピー割り当て演算子はありませんユーザー定義のデストラクタはありません。 PODクラスは、POD構造体またはPODユニオンのいずれかのクラスです。

うわー、これは解析するのが難しいですよね? :)(上記と同じ理由で)組合を除外し、もう少し明確な言い回しをしましょう:

集計クラスは、ユーザー定義のコピー割り当て演算子およびデストラクタがなく、その非静的メンバーが非PODクラス、非PODの配列、または参照でない場合、PODと呼ばれます。

この定義は何を意味しますか? (PODPlain Old Data?)

  • すべてのPODクラスは集合体です。逆に言えば、クラスが集合体でない場合は、PODではありません。
  • クラスは、構造体と同様に、両方のケースで標準用語がPOD-structであっても、PODになります
  • 集約の場合と同様に、クラスがどの静的メンバーを持っているかは関係ありません

例:

0.0

PODクラス、PODユニオン、スカラータイプ、およびそのようなタイプの配列は、PODタイプと総称されます。
PODは多くの点で特別です。いくつかの例を提供します。

  • PODクラスはC構造体に最も近いです。それらとは異なり、PODはメンバー関数と任意の静的メンバーを持つことができますが、どちらもオブジェクトのメモリレイアウトを変更しません。したがって、Cや.NETからでも使用できる多かれ少なかれポータブルな動的ライブラリを作成する場合は、エクスポートされたすべての関数がPOD型のパラメーターのみを取り、返すようにする必要があります。

  • 非PODクラスタイプのオブジェクトの存続期間は、コンストラクターが終了すると開始し、デストラクターが終了すると終了します。 PODクラスの場合、ライフタイムはオブジェクトのストレージが占有されると始まり、ストレージが解放または再利用されると終了します。

  • PODタイプのオブジェクトの場合、オブジェクトのコンテンツをcharまたはunsigned charの配列にmemcpyし、次にmemcpyコンテンツをオブジェクトに戻すと、オブジェクトが元の値を保持します。非PODタイプのオブジェクトにはこのような保証はないことに注意してください。また、memcpyを使用してPODオブジェクトを安全にコピーできます。次の例では、TがPODタイプであると仮定しています。

    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
    };
    
  • gotoステートメント。ご存じかもしれませんが、gotoを介していくつかの変数がまだスコープ内にないポイントから既にスコープ内にあるポイントにジャンプすることは違法です(コンパイラーはエラーを発行する必要があります)。この制限は、変数が非POD型の場合にのみ適用されます。次の例では、f()は不正な形式ですが、g()は正しい形式です。 Microsoftのコンパイラは、この規則には寛大すぎます。どちらの場合でも警告を発するだけです。

    #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
    
  • PODオブジェクトの先頭にパディングがないことが保証されています。言い換えれば、PODクラスAの最初のメンバーがT型である場合、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; } からreinterpret_castからA*まで安全にT*を取得し、最初のメンバーへのポインターを取得できます。

リストは延々と続く…

結論

多くの言語機能は、ご覧のとおり、PODごとに異なる動作をするため、PODが何であるかを正確に理解することが重要です。

531
Armen Tsirunyan

C++ 11の変更点

集計

集計の標準定義はわずかに変更されましたが、それでもほとんど同じです。

集合体は、ユーザー提供のコンストラクター(12.1)なし、非静的データメンバー(9.2)のbrace-or-equal-initializers、プライベートまたは保護された非静的データメンバー(11項)、基本クラスなし(10項)、および仮想関数なし(10.3)。

OK、何が変わったの?

  1. 以前は、集計にser-declaredコンストラクターを含めることはできませんでしたが、現在ではser-providedコンストラクターを含めることはできません。違いはありますか?はい、コンストラクタとデフォルトを宣言できるようになりました。

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

    コンストラクター(または任意の特別なメンバー関数)最初の宣言でデフォルト設定されているはユーザー指定ではないため、これは依然として集約です。

  2. 集計では、非静的データメンバーに対してbrace-or-equal-initializersを使用できません。これは何を意味するのでしょうか?これは、この新しい標準を使用して、次のようにクラスのメンバーを直接初期化できるからです。

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

    この機能を使用すると、基本的に独自のデフォルトコンストラクターを提供するのと同じであるため、クラスは集約されなくなります。

したがって、集計とは何もまったく変わりませんでした。新しい機能に適応した基本的な考え方は同じです。

PODはどうですか?

PODには多くの変更が加えられました。この新しい標準では、PODに関する以前の多くの規則が緩和され、標準での定義の提供方法が根本的に変更されました。

PODの考え方は、基本的に2つの異なるプロパティをキャプチャすることです。

  1. 静的初期化をサポートし、
  2. C++でPODをコンパイルすると、Cでコンパイルされた構造体と同じメモリレイアウトが得られます。

このため、定義はtrivialクラスとstandard-layoutクラスの2つの異なる概念に分割されました。これらはPODよりも便利だからです。現在、標準ではPODという用語はほとんど使用されておらず、より具体的なtrivialおよびstandard-layoutの概念が好まれています。

新しい定義では、基本的に、PODは単純で標準レイアウトを備えたクラスであり、このプロパティはすべての非静的データメンバーに対して再帰的に保持する必要があると述べています。

POD構造体は、自明でないクラスと標準レイアウトクラスの両方である非ユニオンクラスであり、非POD構造体、非PODユニオン型(またはそのような型の配列)型の非静的データメンバーはありません。同様に、PODユニオンは単純なクラスと標準レイアウトクラスの両方であり、タイプが非POD構造体、非PODユニオン(またはそのようなタイプの配列)の非静的データメンバーを持たないユニオンです。 PODクラスは、POD構造体またはPODユニオンのいずれかのクラスです。

これら2つのプロパティのそれぞれについて、個別に詳しく見ていきましょう。

些細なクラス

Trivialは、上記の最初のプロパティです。簡易クラスは静的初期化をサポートします。クラスが簡単にコピーできる場合(取るに足らないクラスのスーパーセット)、memcpyのようなものでその表現をその場所にコピーして、結果が同じであることを期待してもかまいません。

標準では、次のように簡単なクラスを定義しています。

簡単にコピー可能なクラスは、次のクラスです。

—重要なコピーコンストラクター(12.8)はありません。

—重要な移動コンストラクターはありません(12.8)、

—重要なコピー割り当て演算子(13.5.3、12.8)はありません。

—重要な移動割り当て演算子(13.5.3、12.8)はありません。

—些細なデストラクタ(12.4)があります。

トリビアルクラスは、トリビアルなデフォルトコンストラクター(12.1)があり、トリビアルコピーが可能なクラスです。

[注:特に、簡単にコピー可能なクラスまたは簡単なクラスには、仮想関数または仮想基本クラスはありません。— end note]

それで、これらすべての些細なことと些細でないことは何ですか?

クラス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
};

標準レイアウト

Standard-layoutは2番目のプロパティです。標準では、これらは他の言語との通信に役立つと述べています。これは、標準レイアウトクラスが同等のC構造体または共用体と同じメモリレイアウトを持っているためです。

これは、メンバーおよびすべての基本クラスに対して再帰的に保持する必要がある別のプロパティです。通常どおり、仮想関数または仮想基本クラスは許可されていません。これにより、レイアウトがCと互換性がなくなります。

ここでの緩和されたルールは、標準レイアウトクラスに同じアクセス制御を持つすべての非静的データメンバーが必要であるということです。以前は、これらはすべてpublicでなければなりませんでしたが、all privateまたはall protectedである限り、プライベートまたは保護することができます。

継承を使用する場合、継承ツリー全体の1つのみクラスは非静的データメンバーを持つことができ、最初の非静的データメンバーはベースクラスタイプにすることはできません(これによりエイリアス規則が破られる可能性があります)、それ以外の場合は、標準レイアウトクラスではありません。

これは、標準テキストでの定義の流れです。

標準レイアウトクラスは、次のクラスです。

—タイプが非標準レイアウトクラス(またはそのようなタイプの配列)または参照の非静的データメンバーを持たない、

—仮想関数(10.3)および仮想基本クラス(10.1)がありません。

—すべての非静的データメンバーに対して同じアクセス制御(11節)を持ち、

—非標準レイアウトの基本クラスはありません。

—最も派生したクラスに非静的データメンバーがなく、非静的データメンバーを持つ最大1つの基本クラスがあるか、非静的データメンバーを持つ基本クラスがありません。

—最初の非静的データメンバーと同じ型の基本クラスはありません。

標準レイアウト構造体は、クラスキー構造体またはクラスキークラスで定義された標準レイアウトクラスです。

標準レイアウトユニオンは、クラスキーユニオンで定義された標準レイアウトクラスです。

[注:標準レイアウトクラスは、他のプログラミング言語で記述されたコードとの通信に役立ちます。それらのレイアウトは9.2で指定されています。-end note]

そして、いくつかの例を見てみましょう。

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

C++ 14の変更点

参考のために Draft C++ 14標準 を参照することができます。

集合体

これはセクション8.5.1Aggregatesでカバーされており、次のように定義されています。

集合体は、ユーザー提供のコンストラクタ(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は変わりません

POD(普通の古いデータ)構造体の定義は、セクション9クラスで説明されています。

POD構造体110 自明でないクラスでも標準レイアウトクラスでもある非共用体クラスで、非POD構造体、非POD共用体(またはそのような型の配列)の非静的データメンバーはありません。同様に、POD共用体は、自明なクラスでも標準レイアウトクラスでもある共用体であり、非POD構造体型、非POD共用体(またはそのような型の配列)の非静的データ・メンバーはありません。 PODクラスは、POD構造体またはPOD共用体のいずれかであるクラスです。

これはC++ 11と同じ表現です。

C++ 14の標準レイアウトの変更

コメントpodで説明されているように、standard-layoutの定義に依存していますが、C++ 14では変更されましたが、事実の後にC++ 14に適用された欠陥レポートを介して。

DRは3つありました。

それで standard-layout はこのプレC++ 14から来ました:

標準レイアウトクラスは、次のようなクラスです。

  • (7.1)型が非標準レイアウトクラス(またはそのような型の配列)または参照の非静的データメンバを持たない、
  • (7.2)仮想関数([class.virtual])と仮想基底クラス([class.mi])がない、
  • (7.3)全ての非静的データメンバに対して同じアクセス制御(Clause [class.access])を持つ、
  • (7.4)非標準レイアウトの基本クラスはない
  • (7.5)最も派生したクラスおよび非静的データメンバーを持つ最大1つの基本クラスに非静的データメンバーを持たないか、または非静的データメンバーを持つ基本クラスを持たない
  • (7.6)最初の非静的データメンバと同じ型の基本クラスはない。

C++ 14でこれを する

次の場合、クラスSは標準レイアウトクラスです。

  • (3.1)型非標準レイアウトクラス(またはそのような型の配列)または参照の非静的データメンバがない
  • (3.2)仮想関数も仮想基底クラスもない
  • (3.3)すべての非静的データメンバに対して同じアクセス制御を持ちます。
  • (3.4)非標準レイアウトの基本クラスがない
  • (3.5)与えられた型の基本クラスサブオブジェクトを一つだけ持つ
  • (3.6)クラス内のすべての非静的データメンバとビットフィールド、およびその基本クラスが最初に同じクラス内で宣言されている。
  • (3.7)は基底クラスとしての型の集合M(S)の要素を持たない。ここで、任意の型Xに対して、M(X)は次のように定義される。 :M(X)は、Xのオフセットがゼロになる可能性がある、すべての非基本クラスサブオブジェクトの型の集合です。 - end note]
    • (3.7.1)Xが(おそらく継承された)非静的データメンバを持たない非共用体クラス型である場合、集合M(X)は空です。
    • (3.7.2)Xが、サイズがゼロであるか、またはXの最初の非静的データメンバーであるX0型の非静的データメンバーを持つ非共用体クラス型である場合(ここで、そのメンバーは無名共用体であり得る) )、集合M(X)はX0とM(X0)の元からなる。
    • (3.7.3)Xが共用体型の場合、集合M(X)はすべてのM(Ui)とすべてのUiを含む集合の和集合になります。 Xのi番目の非静的データメンバの型.
    • (3.7.4)Xが要素型Xeを持つ配列型である場合、集合M(X)はXeとM(Xe)の要素からなります。
    • (3.7.5)Xが非クラス、非配列型の場合、集合M(X)は空です。
91
Shafik Yaghmour

以下のルールを詳しく説明してください。

私が試してみます:

a)標準レイアウトクラスは、同じアクセス制御を持つすべての非静的データメンバを持たなければならない

それは簡単です:すべての非静的データメンバはallpublicprivate、またはprotectedでなければなりません。 publicprivateを持つことはできません。

それらに対する推論は、「標準レイアウト」と「標準レイアウトではない」との間の区別をするという推論にあります。つまり、物事をメモリに入れる方法を選択する自由をコンパイラに与えることです。それはvtableポインタについてだけではありません。

98年にC++を標準化した当時、彼らは基本的に人々がそれを実装する方法を予測しなければなりませんでした。彼らはさまざまな種類のC++でかなりの実装経験を持っていましたが、物事については確実ではありませんでした。それで彼らは用心深くすることに決めました:コンパイラにできるだけ多くの自由を与えてください。

そのため、C++ 98でのPODの定義は非常に厳密です。それはC++コンパイラにほとんどのクラスのためのメンバレイアウトに関して大きな自由度を与えました。基本的に、PODタイプは特別な場合を意図していました。これはあなたが特別の理由で書いたものです。

C++ 11が開発されていたとき、彼らはコンパイラに関してもっと多くの経験を積んでいました。そして彼らは…C++コンパイラを書く人は本当に怠惰だということに気づきました。彼らはこの自由をすべて持っていました、しかし彼らはdo何もしませんでした。

標準レイアウトの規則は、多かれ少なかれ体系化された一般的な慣習です。ほとんどのコンパイラは、それらを実装するために実際にはあまり変更する必要はありませんでした(対応する型特性に関するいくつかのもの以外)。

さて、public/privateになると、状況は異なります。どのメンバーがpublicprivateであるかを並べ替えることの自由度は、特にビルドのデバッグでは、実際にはコンパイラにとって重要です。そして標準的なレイアウトのポイントは他の言語との互換性があるということなので、デバッグとリリースでレイアウトを異ならせることはできません。

それはそれが本当にユーザーを傷つけないという事実があります。カプセル化されたクラスを作成している場合は、データメンバーすべてがとにかくprivateになる可能性が高いです。一般に、パブリックデータメンバは完全にカプセル化された型には公開されません。そのため、これを実行したい少数のユーザー、つまりその部門を希望するユーザーにとってのみ、これは問題になります。

大きな損失ではありません。

b)継承ツリー全体の1つのクラスだけが非静的データメンバを持つことができます。

この理由は、彼らが再び標準レイアウトを標準化した理由、つまり一般的な方法に帰着します。

実際に物を保管する継承ツリーの2つのメンバーを持つことになると、noの慣例があります。派生クラスの前に基本クラスを置くものもあれば、別の方法でそれを行うものもあります。 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ポインタではなく、パラメータに基づいて動作します。

これもまた大きな損失ではありません。

43
Nicol Bolas

C++ 17での変更点

C++ 17国際標準の最終版 をここからダウンロードしてください

集計

C++ 17では、集計と集計の初期化が拡張および強化されています。標準ライブラリにはstd::is_aggregate型のトレイトクラスも含まれています。これは11.6.1.1節と11.6.1.2節からの正式な定義です(内部参照は省略)。

集合体は、以下を含む配列またはクラスです。
- ユーザー指定、明示的、または継承されたコンストラクタはありません。
- 非公開または保護された非静的なデータメンバーがいない、
- 仮想機能なし
- 仮想、プライベート、または保護ベースクラスはありません。
[注:集合体の初期化では、保護されたプライベートクラスおよびプライベートベースクラスのメンバーまたはコンストラクターにアクセスすることはできません。 - エンドノート]
集合体の要素は次のとおりです。
- 配列の場合、添字の昇順で配列の要素、または
- クラスの場合、宣言順に直接基底クラスが続き、その後に宣言順に匿名共用体のメンバーではない直接の非静的データメンバーが続く.

何が変わった?

  1. 集合体はパブリック、非仮想ベースクラスを持つことができます。さらに、基本クラスが集約であることは必須要件ではありません。それらが集約ではない場合、それらはリスト初期化されています。
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::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. 明示的なデフォルトコンストラクタは許可されていません
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. 継承コンストラクタは許可されていません
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


自明なクラス

自明なクラスの定義は、C++ 14では修正されていなかったいくつかの欠陥に対処するために、C++ 17で作り直されました。変更は技術的なものです。これは12.0.6での新しい定義です(内部参照は廃止)。

簡単にコピー可能なクラスはクラスです。
- 各コピーコンストラクタ、移動コンストラクタ、コピー代入演算子、および移動代入演算子が削除されているか、または簡単な場合
- 少なくとも1つの削除されていないコピーコンストラクタ、移動コンストラクタ、コピー代入演算子、または移動代入演算子
- これには、簡単で削除されていないデストラクタがあります。
自明なクラスとは、自明にコピー可能なクラスであり、1つ以上のデフォルトコンストラクタを持ち、それらはすべて自明または削除され、少なくとも1つは削除されません。 [注:特に、簡単にコピー可能なクラスまたは簡単なクラスには、仮想関数または仮想基本クラスはありません。

変更点

  1. C++ 14では、クラスが自明であるためには、そのクラスは自明ではないコピー/移動コンストラクタ/代入演算子を持つことができませんでした。しかし、暗黙のうちにと宣言されたがデフォルトのコンストラクタ/演算子として自明ではなく、まだdefinedとして削除される可能性があります。たとえば、クラスに、コピーまたは移動できないクラス型のサブオブジェクトが含まれていました。そのような自明でない、削除定義済みのコンストラクタ/演算子が存在すると、クラス全体が自明でなくなります。デストラクタにも同様の問題がありました。 C++ 17では、そのようなコンストラクタ/演算子があってもクラスが自明にコピーできない、つまり自明でないこと、そして自明コピー可能なクラスには自明で削除されていないデストラクタがなければならないことを明確にします。 DR1734DR1928
  2. C++ 14では、簡単にコピー可能なクラス、つまり簡単なクラスでは、すべてのコピー/移動コンストラクタ/代入演算子を削除済みとして宣言することができました。そのようなクラスが標準のレイアウトでもある場合は、std::memcpyを使用して合法的にコピーまたは移動することができます。すべてのコンストラクタ/代入演算子を削除済みとして定義することで、クラスの作成者がクラスをコピー/移動できないことを明確に意図していたにもかかわらず、クラスがまだ些細にコピー可能なクラスの定義を満たしていたためです。したがって、C++ 17では、自明にコピー可能なクラスに、少なくとも1つの自明な、削除されていない(必ずしも一般公開されているわけではない)コピー/移動コンストラクタ/代入演算子が必要であることを示す新しい節があります。 N4148DR1734 を参照してください。
  3. 3番目の技術的変更は、デフォルトコンストラクタに関する同様の問題に関するものです。 C++ 14では、クラスは暗黙のうちに削除されたものとして定義されていたが、それでもまだ些細なクラスであった些細なデフォルトコンストラクタを持つことができました。新しい定義は、自明なクラスが少なくとも1つの自明で削除されていないデフォルトコンストラクタを持たなければならないことを明確にしています。 DR1496を参照してください。

標準レイアウトクラス

標準レイアウトの定義も欠陥レポートに対処するために作り直されました。この変更も技術的なものでした。これは標準(12.0.7)からのテキストです。以前と同様に、内部参照は省略されています。

次の場合、クラスSは標準レイアウトクラスです。
- タイプ非標準レイアウトクラス(またはそのようなタイプの配列)または参照の非静的データメンバーを持たない、
- 仮想関数も仮想基底クラスもありません。
- すべての非静的データメンバーに対して同じアクセス制御を持ちます。
- 非標準レイアウトの基本クラスがない
- 任意の型の基本クラスサブオブジェクトを最大1つ持つ、
- クラス内のすべての非静的データメンバとビットフィールド、およびその基本クラスが最初に同じクラス内で宣言されている。
- 基底クラスとしての型の集合M(S)の要素を持たない(後述)。
M(X)は以下のように定義される。
- Xが(おそらく継承された)非静的データメンバを持たない非共用体クラス型である場合、集合M(X)は空です。
- Xが最初の非静的データメンバの型がX0である(そのメンバは匿名の共用体である可能性がある)非共用体クラスの型である場合、集合M(X)はX0とMの要素(X0).
- Xが共用体型の場合、集合M(X)はすべてのM(Ui)とすべてのUiを含む集合です。 Xのi番目の非静的データメンバーの型です。
- Xが要素型Xeの配列型の場合、集合M(X)はXeとM(Xe)の要素から構成されます。
- Xがクラスでも配列でもない型の場合、集合M(X)は空です。
[注:M(X)は、標準レイアウトクラスでXのゼロオフセットにあることが保証されているすべての非基本クラスサブオブジェクトの型のセットです。 - エンドノート]
[例:

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つのサブオブジェクトが、同じアドレスに割り当てられないようになります。

変更点

  1. 派生ツリーの1つのクラスだけが非静的データメンバを「持つ」という要件は、そのようなデータメンバが最初に宣言されるクラスであり、継承可能なクラスではないことを明確にし、この要件を非静的ビットフィールドに拡張します。また、標準レイアウトクラスには、「任意の型の基本クラスサブオブジェクトが1つしか含まれていない」ことを明確にしました。 DR1813DR1881 を参照してください。
  2. 標準レイアウトの定義では、基本クラスの型を最初の非静的データメンバと同じ型にすることは許可されていません。オフセット0のデータメンバが任意の基本クラスと同じ型を持つという状況を回避するためです。 C++ 17標準では、「標準レイアウトクラスでゼロオフセットになることが保証されているすべての非基本クラスサブオブジェクトの型のセット」のより厳密で再帰的な定義を提供しています。任意の基本クラスの型から DR1672DR2120 を参照してください。

注:新しい言語は公開されたC++ 14標準には含まれていませんが、C++標準化委員会は、不具合報告に基づく上記の変更をC++ 14に適用することを意図しました。これはC++ 17標準です。

29
ThomasMcLeod

C++ 20の変更点

これはまだ早いため、この回答の一部は将来変更される可能性があります。この質問の明確なテーマの残りの部分に続いて、集計の意味と使用法はすべての標準で変わり続けています。地平線上のいくつかの重要な変更があります。

ユーザーが宣言したコンストラクターを持つ型 P1008

C++ 17では、この型は依然として集約です。

struct X {
    X() = delete;
};

したがって、X{}は、コンストラクターの呼び出しではなく、集約の初期化であるため、引き続きコンパイルされます。参照: プライベートコンストラクターがプライベートコンストラクターではない場合

C++ 20では、次の要件から制限が変更されます。

ユーザー提供、explicit、または継承されたコンストラクターはありません

ユーザー宣言または継承されたコンストラクターはありません

これは C++ 20ワーキングドラフト に採用されました。ここのXもリンクされた質問のCも、C++ 20の集計ではありません。

これにより、次の例でヨーヨー効果も得られます。

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

C++ 11/14では、Bは基本クラスのためにnot集合体であったため、B{}は値を実行します-アクセス可能な時点で、B::B()を呼び出すA::A()を呼び出す初期化。これは整形式でした。

C++ 17では、Bが集約になりました。これは、基底クラスが許可されたため、B{}集約が初期化されたためです。これには、{}からAをコピーリストで初期化する必要がありますが、Bのコンテキストの外部からアクセスできません。 C++ 17では、これは不正な形式です(auto x = B();は問題ありません)。

現在のC++ 20では、上記のルールの変更により、Bが再び集約されなくなります(基本クラスではなく、ユーザーが宣言したデフォルトコンストラクタ-デフォルトであっても)。そのため、Bのコンストラクターに戻り、このスニペットは整形式になります。

括弧で囲まれた値のリストから集計を初期化する P96

よくある問題は、emplace()スタイルのコンストラクターを集約で使用することです。

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

emplaceは無効な初期化X(1, 2)を効果的に実行しようとするため、これは機能しません。典型的な解決策は、コンストラクターをXに追加することですが、この提案(現在はCoreで機能している)を使用すると、集約は正しいことを行う合成コンストラクターを効果的に持ち、通常のコンストラクターのように動作します。上記のコードは、C++ 20でそのままコンパイルされます(この機能が承認されると仮定しますが、これはおそらくそうです)。

集計のクラステンプレート引数控除(CTAD) P1021

C++ 17では、これはコンパイルされません。

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

Point p{1, 2}; // error

ユーザーは、すべての集計テンプレートについて独自の控除ガイドを作成する必要があります。

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

しかし、これは何らかの意味で「明らかなこと」であり、基本的には定型的なものなので、言語はこれをあなたのために行います。この変更は2018年11月にEvolutionによって承認されたため、上記の例はおそらくC++ 20でコンパイルされます(ユーザーが提供する控除ガイドは必要ありません)。

8
Barry