構造体、共用体
はじめに
クラスが参照型であるのに対し、 構造体と共用体は値型である。 構造体は、データとそれに関連する操作を単純に集約したものである。
StructDeclaration: struct Identifier ; struct Identifier AggregateBody StructTemplateDeclaration AnonStructDeclaration AnonStructDeclaration: struct AggregateBody
UnionDeclaration: union Identifier ; union Identifier AggregateBody UnionTemplateDeclaration AnonUnionDeclaration AnonUnionDeclaration: union AggregateBody
AggregateBody: { DeclDefsopt }
次の例では、単一の整数フィールドを持つ構造体型を宣言している。
struct S { int i; } void main() { S a; a.i = 3; S b = a; // aをコピーする a.i++; assert(a.i == 4); assert(b.i == 3); }
ローカル変数に対しては、構造体インスタンスはデフォルトでスタック上に割り当てられる。 ヒープ上に割り当てるには、new を使用する。
S* p = new S; assert(p.i == 0);
構造体は、連続して格納される複数のフィールドを含むことができる。 逆に、"共用体"の複数のフィールドは、重複するストレージを使用する。
union U { ubyte i; char c; } void main() { U u; u.i = 3; assert(u.c == '\x03'); u.c++; assert(u.i == 4); }
メンバ
構造体メンバ
構造体の定義には、以下のものが含まれる。
- フィールド
- 静的フィールド
- 匿名構造体および共用体
- メンバ関数
- staticメンバ関数
- コンストラクタ
- デストラクタ
- 不変式
- 演算子オーバーロード
- エイリアス この
- 他の宣言(DeclDefを参照 )
構造体は同一性を持たないように定義される。つまり、 処理系は都合に合わせて構造体のビットコピーを自由に作成できる。
- ビットフィールドは、 bitfieldsテンプレートでサポートされている。
共用体メンバ
共用体の定義には、以下のものが含まれる。
- フィールド
- 静的フィールド
- 匿名構造体および共用体の
- メンバ関数
- staticメンバ関数
- コンストラクタ
- 演算子オーバーロード
- エイリアス
- その他の宣言(DeclDefを参照 )
再帰的な構造体および共用体
構造体および共用体は、それ自身の非静的インスタンスを含むことはできないが、 同じ型のポインタを含むことはできる。
struct S { S* ptr; // OK S[] slice; // OK S s; // エラー S[2] array; // エラー static S global; // OK }
構造体のレイアウト
構造体の非静的データメンバはフィールドと呼ばれる。フィールドは "語彙"順に配置される。フィールドは、有効なアライン属性に従って整列される。 フィールドを整列させるために、名前のないパディングがフィールド間に挿入される。 最初のフィールドとオブジェクトの開始部分との間にはパディングは挿入されない。
ゼロ以外のサイズを持たないフィールドを持つ構造体(いわゆる空構造体)は、サイズが1バイトである。
非静的関数ネスト型D構造体は、 それらの包含スコープのコンテキストにアクセスする
- 構造体のフィールドのデフォルトレイアウトは、 関連するCコンパイラと完全に一致する。
- g++とclang++では、空の構造体の処理方法が異なる。どちらもsizeof から1 を返すが、 clang++はパラメータスタックにプッシュしないが、g++はプッシュする。これは g++とclang++のバイナリ非互換性である。 dmdは、OSXとFreeBSDではclang++の動作に従い、Linuxとその他の Posixプラットフォームではg++の動作に従う。
- clangとgccはどちらも、空の構造体に対してsizeof から0 を返す。clang++とg++でextern "C++"を使用しても、それぞれのCコンパイラの動作に準拠することはない。
- パディングデータはアクセス可能だが、その内容は未定義である。
- ゼロ以外のサイズのフィールドを持たない構造体をextern (C) 関数に渡したり、戻したりしてはならない。 C11 6.7.2.1p8によると、これは未定義の動作である。
- 外部で定義されたレイアウトに一致するように構造体をレイアウトする場合は、 正確に一致するようにalign属性を使用する。 "static assert"を使用して、結果が期待通りになることを確認する。
- パディングの内容はゼロであることが多いが、それに頼ってはならない。
- C言語およびC++コードとのインターフェイスでは、空の構造体の使用は避ける。
- 可変長引数関数のパラメータまたは引数として、空の構造体を使用することは避ける。
プレーン・オールド・データ
構造体または共用体が「プレーン・オールド・データ(POD)」であるためには、以下の条件を満たす必要がある。
- 静的であるか、ネストされていない
- ポストビルト、コピーコンストラクタ、デストラクタ、または代入演算子を持たない
- それ自体がPODではないフィールドを持たない
不明瞭な構造体および共用体
不明瞭な構造体および共用体の宣言にはAggregateBody が存在しない。
struct S; union U; struct V(T); union W(T);
メンバはユーザーに対して完全に隠蔽されているため、 これらの型に対する操作は、その型の内容に関する知識を必要としないものに限られる。 例えば:
struct S; S.sizeof; // エラー、サイズがわからない S s; // エラー、未知のコンテンツを初期化できない S* p; // OK、メンバーに関する知識は必要ない
初期化
構造体のデフォルトの初期化
構造体のフィールドは、デフォルトでは、 そのフィールドの初期化子があればそれに従って初期化され、初期化子が指定されていない場合は、 そのフィールドの型のデフォルトの初期化子に従って初期化される。
struct S { int a = 4; int b; } S x; // x.aは4、x.bは0に設定される
デフォルトの初期化子はコンパイル時に評価される。
構造体の静的初期化
StructInitializer: { StructMemberInitializersopt } StructMemberInitializers: StructMemberInitializer StructMemberInitializer , StructMemberInitializer , StructMemberInitializers StructMemberInitializer: NonVoidInitializer Identifier : NonVoidInitializer
StructInitializer が指定された場合、 各 StructMemberInitializer は対応するフィールドを初期化する。
- Identifier:NonVoidInitializer構文を使用するStructMemberInitializerは 任意の順序で記述できる。識別子はフィールド名と一致していなければならない。
- 最初の StructMemberInitializer で Identifier が指定されていない場合、 StructDeclaration の最初のフィールドを参照する。
- 識別子を指定しない後続のNonVoidInitializerは、 前の StructMemberInitializer で参照されたフィールドの次のフィールド(語彙順)を参照する。
StructMemberInitializerでカバーされていないフィールドは、デフォルトで初期化される。
struct S { int a, b, c, d = 7; } S r; // r.a = 0, r.b = 0, r.c = 0, r.d = 7 S s = { a:1, b:2 }; // s.a = 1, s.b = 2, s.c = 0, s.d = 7 S t = { c:4, b:5, a:2, d:5 }; // t.a = 2, t.b = 5, t.c = 4, t.d = 5 S u = { 1, 2 }; // u.a = 1, u.b = 2, u.c = 0, u.d = 7 S v = { 1, d:3 }; // v.a = 1, v.b = 0, v.c = 0, v.d = 3 S w = { b:1, 3 }; // w.a = 0, w.b = 1, w.c = 3, w.d = 7
フィールドを複数回初期化することはエラーである。
S x = { 1, a:2 }; // エラー: フィールド`a`のイニシャライザーが重複している
共用体のデフォルト初期化
共用体は既定で、 最初のフィールドの初期化子として指定されたものが使用され、指定されていない場合は、 最初のフィールドの型の既定の初期化子が使用される。 共用体が最初のフィールドよりも大きい場合、残りのビットは 0に設定される。
union U { int a = 4; long b; } U x; // x.aは4に設定され、x.bは実装で定義された値に設定される
最初のフィールド以外のメンバに初期化子を指定することはエラーである。
union V { int a; long b = 4; } // エラー: デフォルトの初期化値`4`を持つ共用体のフィールド`b`はフィールド`a`の前になければならない union W { int a = 4; long b = 5; } // エラー: `a`と`b`のデフォルト初期化が重複している
既定の初期化子はコンパイル時に評価される。
共用体の静的初期化
共用体は、 構造体と同様に初期化されるが、 初期化子は1つだけしか許可されない。 メンバ初期化子が識別子を指定していない場合、 共用体の最初のメンバが初期化される。
union U { int a; double b; } U u = { 2 }; // u.a = 2 U v = { b : 5.0 }; // v.b = 5.0
U w = { 2, 3 }; // エラー: フィールド`a`と`b`の重複初期化
共用体が初期化されるフィールドよりも大きい場合、残りのビットは 0に設定される。
構造体の動的初期化
静的初期化子の構文は、 静的変数以外の初期化にも使用できる。 初期化子は、コンパイル時に評価可能である必要はない。
struct S { int a, b, c, d = 7; } void test(int i) { S q = { 1, b:i }; // q.a = 1, q.b = i, q.c = 0, q.d = 7 }
構造体は、同じ型の別の値から動的に初期化することもできる。
struct S { int a; } S t; // デフォルトで初期化される t.a = 3; S s = t; // s.aは3に設定されている
構造体にコンストラクタがあり、 その構造体が異なる型の値で初期化される場合、 コンストラクタが呼び出される。
struct S { int a; this(int v) { this.a = v; } } S s = 3; // Sのコンストラクタを使ってs.aを3に設定する
構造体がコンストラクタを持たないが、 opCall構造体に対してオーバーライドされており、構造体が異なる型の値で初期化されている場合、opCall 演算子が呼び出される。
struct S { int a; static S opCall(int v) { S s; s.a = v; return s; } static S opCall(S v) { assert(0); } } S s = 3; // S.opCall(int)を使ってs.aを3に設定する S t = s; // t.aを3に設定、S.opCall(S)は呼び出されない
共用体の動的初期化
静的初期化子の構文は、 静的変数以外の初期化にも使用できる。 初期化子は、コンパイル時に評価可能である必要はない。
union U { int a; double b; } void test(int i) { U u = { a : i }; // u.a = i U v = { b : 5.0 }; // v.b = 5.0 }
構造体リテラル
構造体リテラルは、構造体の名前の後に 括弧で囲まれた名前付き引数リストが続く。
struct S { int x; float y; } S s1 = S(1, 2); // フィールドxを1、フィールドyを2に設定する S s2 = S(y: 2, x: 1); // 上記と同じ assert(s1 == s2);
構造体にコンストラクタ またはメンバ関数名opCall が存在する場合、 その構造体の構造体リテラルは使用できない。 この問題の回避策については、 opCall 演算子オーバーロード を参照のこと。
構造体リテラルは、構文上は関数呼び出しに似ている。
- 最初の引数に名前がない場合、それは語彙的に最初に定義された構造体フィールドに割り当てられる。
- 名前付き引数は、同じ名前の構造体フィールドに割り当てられる。 そのようなフィールドが存在しない場合はエラーとなる。
- その他の引数は、直前の引数の構造体フィールドに対して相対的に、次に語彙的に定義された構造体フィールドに割り当てられる。 つまり、直前の引数が最後の構造体フィールドに割り当てられた場合など、そのようなフィールドが存在しない場合はエラーとなる。
- また、フィールドに複数回代入することもエラーである。
- 値が割り当てられていないフィールドは、それぞれのデフォルトの初期化子で初期化される。
構造体に共用体フィールドがある場合、 共用体のメンバは1つだけ構造体リテラル内で初期化できる。 これは共用体リテラルの動作と一致する。
struct S { int x = 1, y = 2, z = 3; } S s0 = S(y: 5, 6, x: 4); // フィールド`z`には`6`が代入され、これは`y`の後に来る assert(s0.z == 6); S s1 = S(y: 5, z: 6); // フィールドxは割り当てられず、デフォルトのイニシャライザー`1`に設定される assert(s1.x == 1); //S s2 = S(y: 5, x: 4, 5); // エラー: フィールド`y`が2回代入されている //S s3 = S(z: 2, 3); // エラー: フィールド`z`以上にフィールドがない
共用体リテラル
共用体リテラルは構造体リテラルに似ているが、初期化子式で初期化できるのは1つのフィールドのみである。 共用体の残りのメモリはゼロで初期化される。
union U { byte a; char[2] b; } U u = U(2); assert(u.a == 2); assert(u.b == [2, 0]);
匿名構造体および共用体
匿名構造体または共用体は、struct またはunion の後に識別子を省略することで、親クラス、構造体、または共用体のメンバとして宣言できる。 匿名構造体は、親型に順次格納されたフィールドを宣言する。 匿名共用体は、親型に重複するフィールドを宣言する。
匿名共用体は、クラスまたは構造体の内部で、 親フィールドを別の共用体型で指定することなく、フィールドのメモリを共有するのに 役立つ。
struct S { int a; union { byte b; char c; } } S s = S(1, 2); assert(s.a == 1); assert(s.b == 2); assert(s.c == 2); // `b`と重複する
逆に、匿名構造体は共用体内で 順次格納される複数のフィールドを宣言するのに役立つ。
union U { int a; struct { uint b; bool c; } } U u = U(1); assert(u.a == 1); assert(u.b == 1); // `a`と重複する assert(u.c == false); // 重なっていない
構造体のプロパティ
Name | Description |
---|---|
.alignof | サイズ境界構造体は整列する必要がある |
構造体インスタンスのプロパティ
Name | Description |
---|---|
.tupleof | すべての構造体フィールドのl値シーケンス - クラスベースの例については、こちらを参照のこと。 |
構造体フィールドのプロパティ
Name | Description |
---|---|
.offsetof | 構造体の先頭からのフィールドのオフセット(バイト単位) |
const、immutable、および共有構造体
構造体宣言では、const 、immutable 、shared のいずれかのストレージクラスを使用できる。 これは、const 、immutable 、shared として構造体の各メンバを宣言するのと同等の 効果がある。
const struct S { int a; int b = 2; } void main() { S s = S(3); // s.aを3に初期化する S t; // t.aを0に初期化する t = s; // エラー、t.aとt.bはconstなので変更できない。 t.a = 4; // エラー、t.aはconstである }
共用体コンストラクタ
共用体は構造体と同じ方法で作成される。
構造体コンストラクタ
構造体コンストラクタは、 静的初期化や 構造体リテラルで許可されているよりも複雑な構造体の構築が必要な場合に 構造体のインスタンスを初期化するために使用される。
コンストラクタは、関数名this で定義され、戻り値は持たない。 文法はクラスコンストラクタと同じである。
構造体コンストラクタは、構造体の名前に続けて パラメータ
パラメータリストが空の場合、 構造体インスタンスはデフォルトで初期化される。
struct S { int x, y = 4, z = 6; this(int a, int b) { x = a; y = b; } } void main() { S a = S(4, 5); // S.this(4, 5)を呼び出す: a.x = 4, a.y = 5, a.z = 6 S b = S(); // デフォルトの初期化: b.x = 0, b.y = 4, b.z = 6 S c = S(1); // エラー、this(int)が見つからない }
名前付き引数はコンストラクタに渡され、構造体のフィールド名ではなく、パラメータ名と一致する。
struct S { int x; int y; this(int y, int z) { this.x = y; this.y = z; } } S a = S(x: 3, y: 4); // エラー: コンストラクタに`x`というパラメータがない S b = S(y: 3, 4); // `y: 3`はパラメータ`y`を通してフィールド`x`を設定する
デフォルトコンストラクタ(つまり、ParameterListが空のコンストラクタ)は 許可されない。
struct S { int x; this() { } // エラー、struct default constructor not allowed }
委譲コンストラクタ
コンストラクタは、同じ構造体の別のコンストラクタを呼び出すことができる。 これは、共通の初期化を共有するために行われる。これを 「委譲コンストラクタ」と呼ぶ。
struct S { int j = 1; long k = 2; this(long k) { this.k = k; } this(int i) { // この時点で: j=1, k=2 this(6); // コンストラクタ呼び出しの委譲 // この時点で: j=1, k=6 j = i; // この時点で: j=i, k=6 } }
以下の制限が適用される。
- コンストラクタのコードに委譲コンストラクタの呼び出しが含まれる場合、
コンストラクタのすべての実行パスにおいて、必ず1つの
委譲コンストラクタ呼び出しが実行される必要がある。
struct S { int a; this(int i) { } this(char c) { c || this(1); // エラー、すべてのパスにない } this(wchar w) { (w) ? this(1) : this('c'); // OK } this(byte b) { foreach (i; 0 .. b) { this(1); // エラー、ループ内部 } } }
- 委譲コンストラクタの呼び出しを行う前に、this を暗黙的または明示的に参照することはできない。
- 委譲コンストラクタが戻ると、すべてのフィールドが 構築されたとみなされる。
- 委任コンストラクタ呼び出しはラベルの後ろに記述することはできない。
構造体のインスタンス化
構造体のインスタンスが作成されると、以下の手順が実行される。
- 構造体定義で指定された値を使用して、生のデータが静的に初期化される。 この操作は、静的バージョンのオブジェクトを 新たに割り当てられたものにメモリコピーするのと同義である。
- 構造体に対してコンストラクタが定義されている場合、 引数リストに一致するコンストラクタが呼び出される。
- 構造体の不変条件のチェックが有効になっている場合、構造体の不変条件は コンストラクタの最後に呼び出される。
コンストラクタ属性
コンストラクタ修飾子 (const 、immutable 、またはshared) は、 その特定の修飾子を持つオブジェクトのインスタンスを構築する。
struct S1 { int[] a; this(int n) { a = new int[](n); } } struct S2 { int[] a; this(int n) immutable { a = new int[](n); } } void main() { // ミュータブル・コンストラクタはミュータブル・オブジェクトを生成する。 S1 m1 = S1(1); // 構築されたミュータブル・オブジェクトは暗黙のうちにconstに変換可能である。 const S1 c1 = S1(1); // 構築されたミュータブル・オブジェクトは暗黙のうちにイミュータブルに変換できない。 immutable i1 = S1(1); // エラー // ミュータブル・コンストラクタはイミュータブル・オブジェクトを構築できない。 auto x1 = immutable S1(1); // エラー // イミュータブル・コンストラクタがイミュータブル・オブジェクトを生成する。 immutable i2 = immutable S2(1); // イミュータブル・コンストラクタはミュータブル・オブジェクトを構築できない。 auto x2 = S2(1); // エラー // 構築されたイミュータブル・オブジェクトは暗黙的にミュータブルに変換できない。 S2 m2 = immutable S2(1); // エラー // 構築された不変のオブジェクトは暗黙のうちにconstに変換される。 const S2 c2 = immutable S2(1); }
コンストラクタは、異なる属性でオーバーロードすることができる。
struct S { this(int); // 共有でないミュータブル・コンストラクタ this(int) shared; // 共有ミュータブル・コンストラクタ this(int) immutable; // 不変のコンストラクタ } void fun() { S m = S(1); shared s = shared S(2); immutable i = immutable S(3); }
純粋コンストラクタ
コンストラクタが唯一無二のオブジェクトを作成できる場合(すなわち、それがpure である場合)、 そのオブジェクトは暗黙的に任意の修飾子に変換できる。
struct S { this(int) pure; // 定義に基づいて、これはミュータブル・オブジェクトを生成する。しかし、 // 生成されたオブジェクトは、変更可能なグローバル・データを含むことはできない。 // したがって、生成されるオブジェクトは一意である。 this(int[] arr) immutable pure; // 定義に基づけば、これは不変のオブジェクトを生成する。しかし、 // 引数int[]は生成されたオブジェクトには現れないので、 // 暗黙のうちにイミュータブルに変換されることはない。また、不変の // グローバル・データを格納することもできない。 // したがって、生成されるオブジェクトは一意である。 } void fun() { immutable i = immutable S(1); // this(int) pureが呼ばれる shared s = shared S(1); // this(int) pureが呼ばれる S m = S([1,2,3]); // this(int[]) immutable pureが呼ばれる }
デフォルトの構造体構築の無効化
構造体のコンストラクタが@disable という注釈が付けられ、 空のParameterList を持つ場合、その構造体はデフォルトのコンストラクションが無効になっている。 この構造体を構築できる唯一の方法は、空ではない ParameterList を持つ別のコンストラクタを呼び出すことである 。
デフォルトコンストラクタが無効で、他のコンストラクタも存在しない構造体は、 VoidInitializer を使用してインスタンス化する以外に方法がない。
無効化されたデフォルトコンストラクタには、FunctionBodyを指定できない。
フィールドのデフォルトの構築が無効になっている場合、構造体のデフォルトの構築も 無効になります。
struct S { int x; // デフォルトの構築を無効にする @disable this(); this(int v) { x = v; } } struct T { int y; S s; } void main() { S s; // エラー: デフォルトの構築は無効になっている S t = S(); // エラー: also disabled S u = S(1); // `S.this(1)`を呼び出して構築する S v = void; // 初期化されないが、許可される S w = { 1 }; // エラー: コンストラクタが存在するので{ }は使えない S[3] a; // エラー: デフォルトのコンストラクタは無効になっている S[3] b = [S(1), S(20), S(-2)]; // OK T t; // エラー: デフォルトのコンストラクタは無効になっている }
コンストラクタ内のフィールド初期化
コンストラクタの本体で委譲コンストラクタが呼び出された場合、 すべてのフィールドの代入は代入とみなされる。 そうでなければ、最初のフィールドの代入は その初期化であり、field = expressionの形式の代入はtypeof(field)(expression) と同等に扱われる。 フィールドの値は、初期化または委譲コンストラクタによる構築の前に読み取られる可能性がある
struct S { int num; int ber; this(int i) { num = i + 1; // 初期化 num = i + 2; // 代入 ber = ber + 1; // 初期化の前に読み込めばOK } this(int i, int j) { this(i); num = i + 1; // 代入 } }
フィールドの型に opAssign メソッドがある場合、初期化には使用されません。
struct A { this(int n) {} void opAssign(A rhs) {} } struct S { A val; this(int i) { val = A(i); // valはA(i)の値に初期化される val = A(2); // val.opAssign(A(2))に書き換えられる } }
フィールドの型が変更可能でない場合、複数回の初期化は拒否される。
struct S { immutable int num; this(int) { num = 1; // OK num = 2; // エラー: 不変への代入 } }
フィールドが1つのパスで初期化されている場合は、すべてのパスで初期化する必要がある。
struct S { immutable int num; immutable int ber; this(int i) { if (i) num = 3; // 初期化 else num = 4; // 初期化 } this(long j) { j ? (num = 3) : (num = 4); // OK j || (ber = 3); // エラー: 1つのパスのみで初期化された j && (ber = 3); // エラー: 1つのパスのみで初期化された } }
フィールドの初期化は、ループ内やラベルの後では実行できない。
struct S { immutable int num; immutable string str; this(int j) { foreach (i; 0..j) { num = 1; // エラー: フィールドの初期化はループでは許可されない } size_t i = 0; Label: str = "hello"; // エラー: フィールドの初期化はラベルの後では許可されない if (i++ < 2) goto Label; } this(int j, int k) { switch (j) { case 1: ++j; break; default: break; } num = j; // エラー: `case`と`default`もラベルである } }
フィールドの型がデフォルトの構築を無効にしている場合は、コンストラクタで初期化する必要がある。
struct S { int y; @disable this(); } struct T { S s; this(S t) { s = t; } // OK this(int i) { this('c'); } // OK this(char) { } // エラー: sが初期化されていない }
構造体コピーコンストラクタ
コピーコンストラクタは、同じ型の別のインスタンスからstruct インスタンスを初期化するために使用される。 コピーコンストラクタを定義するstruct は PODではない。
以下の要件を満たす場合、コンストラクタ宣言はコピーコンストラクタ宣言となる。
- デフォルト引数なしのパラメータを正確に1つ取り、 それに続く任意の数のパラメータにはデフォルト引数がある。
- 最初のパラメータはref パラメータである。
- 最初のパラメータの型は、 typeof(this)、オプションで1つ以上の "型修飾子"が適用されることもある。
- テンプレートコンストラクタの宣言ではない。
struct A { this(ref return scope A rhs) {} // コピーコンストラクタ this(ref return scope const A rhs, int b = 7) {} // デフォルトパラメータを持つコピーコンストラクタ }
コピーコンストラクタは、通常のコンストラクタと同様に型チェックが行われる。
コピーコンストラクタが定義されている場合、以下の状況では暗黙的に呼び出される。
- 変数が明示的に初期化される場合:
- パラメータが関数に値として渡される場合:
- 関数から値としてパラメータが返され、名前付き返却値最適化(NRVO)が 実行できない場合:
struct A { int[] arr; this(ref return scope A rhs) { arr = rhs.arr.dup; } } void main() { A a; a.arr = [1, 2]; A b = a; // コピーコンストラクタが呼び出される b.arr[] += 1; assert(a.arr == [1, 2]); // aは変更されない assert(b.arr == [2, 3]); }
struct A { this(ref return scope A another) {} } void fun(A a) {} void main() { A a; fun(a); // コピーコンストラクタが呼び出される }
struct A { this(ref return scope A another) {} } A fun() { A a; return a; // NRVO、コピーコンストラクタは呼ばれない } A a; A gun() { return a; // NRVOを実行できない、このように書き換える: return (A __tmp; __tmp.copyCtor(a)); } void main() { A a = fun(); A b = gun(); }
コピーの無効化
struct に対してコピーコンストラクタが定義されている場合(または@disable とマークされている場合)、コンパイラは もはやそのstruct に対してデフォルトのコピー/ブリッティングコンストラクタを暗黙的に生成しない。
struct A { int[] a; this(ref return scope A rhs) {} } void fun(immutable A) {} void main() { immutable A a; fun(a); // エラー: コピーコンストラクタは、(immutable) immutable型で呼び出すことはできない }
struct A { @disable this(ref A); } void main() { A a; A b = a; // エラー: コピーコンストラクタは無効である }
union U にコピーコンストラクタを定義するフィールドがある場合、U型のオブジェクトが コピーによって初期化されると、エラーが発生する。このルールは重複フィールド(匿名共用体)にも適用される 。
struct S { this(ref S); } union U { S s; } void main() { U a; U b = a; // エラー、Uのコピーコンストラクタを生成できなかった }
コピーコンストラクタ属性
コピーコンストラクタは、パラメータ(修飾されたソースからのコピー)またはコピーコンストラクタ自体(修飾された宛先へのコピー)に異なる修飾子を適用してオーバーロードすることができる。
struct A { this(ref return scope A another) {} // 1 - ソースがミュータブル、デスティネーションがミュータブル this(ref return scope immutable A another) {} // 2 - ソースがイミュータブル、デスティネーションがミュータブル this(ref return scope A another) immutable {} // 3 - ソースがミュータブル、デスティネーションが不変 this(ref return scope immutable A another) immutable {} // 4 - ソースが不変、デスティネーションが不変 } void main() { A a; immutable A ia; A a2 = a; // 1を呼び出す A a3 = ia; // 2を呼び出す immutable A a4 = a; // 3を呼び出す immutable A a5 = ia; // 4を呼び出す }
inout 修飾子はコピーコンストラクタのパラメータに適用され、 変更可能、const 、またはimmutable 型が同じように扱われることを指定する。
struct A { this(ref return scope inout A rhs) immutable {} } void main() { A r1; const(A) r2; immutable(A) r3; // `inout`がワイルドカードのように働くので、すべて同じコピーコンストラクタを呼び出す immutable(A) a = r1; immutable(A) b = r2; immutable(A) c = r3; }
暗黙のコピーコンストラクタ
struct Sに対して、以下の条件がすべて満たされる場合、コンパイラによって暗黙的にコピーコンストラクタが生成される。
- S 明示的にコピーコンストラクタを宣言していないこと、
- S 少なくとも1つの直接メンバを定義し、そのメンバが ( によって)他のメンバと重複していない。union
上記の制限が満たされると、以下のコピーコンストラクタが生成される。
this(ref return scope inout(S) src) inout { foreach (i, ref inout field; src.tupleof) this.tupleof[i] = field; }
生成されたコピーコンストラクタが型チェックに失敗した場合は、@disable 属性が割り当てられる。
構造体 Postblits
Postblit: this ( this ) MemberFunctionAttributesopt FunctionBody
警告: ポストブライトはレガシーと見なされ、新しいコードには推奨されない。 コードは、前のセクションで定義されたコピーコンストラクタを使用すべきである。 後方互換性の理由から、struct で 明示的にコピーコンストラクタとポストブライトの両方を定義すると、ポストブライトは暗黙のコピーに対してのみ使用される。 ただし、ポストブライトが無効になっている場合は、コピーコンストラクタが使用される。 使用されます。構造体がコピーコンストラクタ(ユーザー定義または生成)を定義し、 ポストブライトを定義するフィールドを持つ場合、ポストブライトが コピーコンストラクタよりも優先される旨の警告が出されます。
コピー構造体は、 同じ型の別の構造体インスタンスから構造体インスタンスを初期化することと定義される。 コピー構造体は2つの部分に分けられる。
- フィールドのブリッティング、すなわち
- postblitを実行して結果をコピーする
最初の部分は言語によって自動的に行われ、 2番目の部分はpostblit関数が構造体に対して定義されている場合に実行される。 postblitは、コピー先の構造体オブジェクトのみにアクセスでき、 コピー元にはアクセスできない。 その役割は、必要に応じてコピー先を「修復」することであり、例えば 参照データのコピーを作成したり、参照カウントをインクリメントしたりする などである。例えば:
struct S { int[] a; // 配列はこのインスタンスによって私有される this(this) { a = a.dup; } }
struct postblit を無効にすると、オブジェクトはコピーできなくなる。
struct T { @disable this(this); // 無効にするとTはコピーできなくなる } struct S { T t; // コピー不可能なメンバはSもコピー不可能にする } void main() { S s; S t = s; // エラー、Sはコピー可能ではない }
構造体のレイアウトによっては、コンパイラは以下の内部postblit関数を生成することがある 。
- void __postblit()。コンパイラは、明示的に定義されたpostblitthis(this) にこの名前を割り当てることで、 通常の関数として正確に扱えるようにする。 構造体がpostblitを定義している場合、__postblit という名前の関数を定義することはできない。 これは、名前の競合によりコンパイルエラーが発生するからである。
- void __fieldPostblit()。構造体X に、少なくとも1つのstruct メンバがあり、それが(明示的または暗黙的に)ポストブライトを定義している場合、 X に対してフィールドポストブライトが生成され、 宣言順に構造体フィールドのすべてのポストブライトを呼び出す。
- void __aggrPostblit()。構造体に明示的に定義されたポストブライトがあり、 かつポストブライト(明示的または暗黙的)を持つ構造体メンバが少なくとも1つある場合、__fieldPostblit が最初に呼び出され、次に__postblit が呼び出される集約ポストブライトが生成される。
- void __xpostblit()。フィールドおよび集約ポストブリットは、 "構造体"用に生成されるが、実際の"構造体"のメンバーではない。 これらを呼び出すことができるように、コンパイラは内部的にエイリアスを作成する。このエイリアスは__xpostblitと呼ばれ、 "構造体"のメンバーであり、最も包括的な生成ポストブリットを指す。
// エイリアス__xpostblit = __postblitを持つ構造体 struct X { this(this) {} } // X.__xpostblitへの呼び出しを含む // エイリアス__xpostblit = __fieldPostblitを持つ構造体 struct Y { X a; } // エイリアス__xpostblit = __aggrPostblitを持つ構造体で、 // Y.__xpostblitへのコールと Z.__postblitへのコールを含む struct Z { Y a; this(this) {} } void main() { // Xは__postblitと__xpostblitを持っている(__postblitを指している) static assert(__traits(hasMember, X, "__postblit")); static assert(__traits(hasMember, X, "__xpostblit")); // Yは__postblitを持っていないが、__xpostblit(__fieldPostblitを指す)を持っている static assert(!__traits(hasMember, Y, "__postblit")); static assert(__traits(hasMember, Y, "__xpostblit")); // __fieldPostblitは構造体のメンバではない static assert(!__traits(hasMember, Y, "__fieldPostblit")); // Zは__postblitと__xpostblitを持つ(__aggrPostblitを指す) static assert(__traits(hasMember, Z, "__postblit")); static assert(__traits(hasMember, Z, "__xpostblit")); // __aggrPostblitは構造体のメンバではない static assert(!__traits(hasMember, Z, "__aggrPostblit")); }
xml-ph-0000@deepl.internal を定義せず、また、それを間接的に定義するフィールドを持たない構造体に対しては、上記のポストブライトのいずれも定義されない this(this) を定義せず、またそれを間接的に定義するフィールドを持たない構造体に対しては、 ポストブライトも定義されない。構造体がポストブライト(暗黙的または明示的)を定義しないが、 内部的に生成されたポストブライトと同じ名前/シグネチャを使用する関数を定義する 場合、コンパイラはその関数が 実際のポストブライトではないことを識別でき、構造体がコピーされる際にその関数への呼び出しを挿入しない。 "例:"
struct X {} int a; struct Y { int a; X b; void __fieldPostPostblit() { a = 42; } } void main() { static assert(!__traits(hasMember, X, "__postblit")); static assert(!__traits(hasMember, X, "__xpostblit")); static assert(!__traits(hasMember, Y, "__postblit")); static assert(!__traits(hasMember, Y, "__xpostblit")); Y y; auto y2 = y; assert(a == 0); // __fieldPostBlitが呼び出されない }
ポストブライトはオーバーロードできない。2つ以上のポストブライトが定義されている場合、 たとえシグネチャが異なっていても、コンパイラは__postblit 名を両方に割り当て、後に競合する関数名エラーを発行する
struct X { this(this) {} this(this) const {} // エラー: 関数X.__postblitが関数X.__postblitと衝突する }
以下では、修飾されたポストブライト定義の動作について説明する。
- const。ポストブライトがconst で修飾されている場合、this(this) const; やconst this(this); のように、ポストブライトは "変更可能な(修飾されていない)"const 、 "immutable オブジェクトに対しては正常に呼び出されるが、ポストブライトはオブジェクトを変更できない。 なぜなら、オブジェクトはconst として認識されるためである。したがって、const ポストブライトは "有用性が限られている"。 "例:"
- immutable。ポストブライトがimmutableで修飾されている場合、this(this) immutable やimmutable this(this)のように コードは不正となる。immutable のポストブライトは コンパイル段階を通過するが、呼び出すことはできない。例:
- shared。shared で修飾されたpostblitの場合、this(this) shared やshared this(this) のように、sharedオブジェクトのみが postblitを呼び出すことができる。共有されていないオブジェクトでpostblitを試みると、
struct S { int n; this(this) const { import std.stdio : writeln; writeln("postblit called"); //++n; // エラー: `const`関数内でthis.nを変更できない } } void main() { S s1; auto s2 = s1; const S s3; auto s4 = s3; immutable S s5; auto s6 = s5; }
struct Y { // どこにも呼び出されていないので、エラーは発生しない this(this) immutable { } } struct S { this(this) immutable { } } void main() { S s1; auto s2 = s1; // エラー: 不変のメソッド`__postblit`は変更可能なオブジェクトでは呼び出せない const S s3; auto s4 = s3; // エラー: 不変のメソッド`__postblit`は変更可能なオブジェクトでは呼び出せない immutable S s5; auto s6 = s5; // エラー: 不変のメソッド`__postblit`は変更可能なオブジェクトでは呼び出せない }
struct S { this(this) shared { } } void main() { S s1; auto s2 = s1; // エラー: 共有メソッド`__postblit`は共有でないオブジェクトでは呼び出せない const S s3; auto s4 = s3; // エラー: 共有メソッド`__postblit`は共有でないオブジェクトでは呼び出せない immutable S s5; auto s6 = s5; // エラー: 共有メソッド`__postblit`は共有でないオブジェクトでは呼び出せない // 共有オブジェクトに対する共有ポストブリットの呼び出しは許可される shared S s7; auto s8 = s7; }
修飾されていないpostblitは、immutable またはconst として構造体がインスタンス化された場合でも呼び出されるが、shared として構造体がインスタンス化された場合は、コンパイラがエラーを発行する
struct S { int n; this(this) { ++n; } } void main() { immutable S a; // shared S a; => エラー : 共有でないメソッドは共有オブジェクトを使用して呼び出すことができない auto a2 = a; import std.stdio: writeln; writeln(a2.n); // 1を表示 }
ポストブライトの観点では、構造体定義に修飾子を付けると、 ポストブライトに明示的に修飾子を付けるのと同じ結果になる。
以下の表は、ポストブライトに関連するグループ化修飾子のすべての可能性を列挙したものである。 ポストブライトを正常に呼び出すために使用する必要があるオブジェクトの型に関連する。
呼び出されるオブジェクトの型 | const | immutable | shared |
任意のオブジェクト型 | ✔ | ||
呼び出し不可 | ✔ | ||
共有オブジェクト | ✔ | ||
呼び出し不可 | ✔ | ✔ | |
共有オブジェクト | ✔ | ✔ | |
呼び出せない | ✔ | ✔ | |
呼び出せない | ✔ | ✔ | ✔ |
const とimmutable を使用して明示的にポストブライトを修飾する場合、 this(this) const immutable; やconst immutable this(this); のようにポストブライトを明示的に修飾する場合、 修飾子の宣言順序は関係ないが、コンパイラは競合する属性エラーを生成する。 しかし、構造体をconst/immutableと宣言し、 ポストブライトをimmutable/const と宣言すると、 両方の修飾子をポストブライトに適用する効果が得られる。いずれの場合も、ポストブライトは より制限の厳しい修飾子で修飾される。その修飾子はimmutable である。
ポストブライト__fieldPostblit と__aggrPostblit 暗黙的な修飾子なしで生成され、構造体のメンバとは見なされない。 このため、const またはimmutable を使用して構造体宣言全体を修飾しても、 前述の postblits にはまったく影響しない。しかし、__xpostblitは 構造体のメンバであり、他の postblits のエイリアスであるため、 構造体に適用された修飾子はエイリアス postblit に影響する。
struct S { this(this) { } } // `__xpostblit`集約ポストブリットのエイリアスなので、`const`が適用される。 // しかし、集約されたpostblitは修飾子が適用されていないフィールド // postblitを呼び出すため、修飾子の不一致エラーが発生する const struct B { S a; // エラー : 変更可能なメソッドB.__fieldPostblitは、constオブジェクトを使用して呼び出すことはできない this(this) { } } // `__xpostblit`はフィールドpostblitをエイリアスする; エラーなし const struct B2 { S a; } // Bと似ている immutable struct C { S a; // エラー : 変更可能なメソッドC.__fieldPostblitは、不変のオブジェクトを使用して呼び出すことはできない this(this) { } } // B2と同様、コンパイルされる immutable struct C2 { S a; }
上記の状況では、エラーには行番号が含まれない。なぜなら、 エラーは生成されたコードに関するものだからだ。
構造体全体に修飾子を付けると、shared 、属性が正しく生成されたポストブリットに伝播される。
shared struct A { this(this) { import std.stdio : writeln; writeln("the shared postblit was called"); } } struct B { A a; } void main() { shared B b1; auto b2 = b1; }
共用体には、postblit を持つフィールドが存在する可能性がある。ただし、共用体自体には決して postblit は存在しない。 共用体をコピーしても、どのフィールドに対しても postblit が呼び出されることはない。 postblit の呼び出しが必要な場合は、プログラマーが明示的に挿入しなければならない。
struct S { int count; this(this) { ++count; } } union U { S s; } void main() { U a = U.init; U b = a; assert(b.s.count == 0); b.s.__postblit; assert(b.s.count == 1); }
構造体のデストラクタ
デストラクタは、オブジェクトがスコープ外に出た場合、または デフォルトでは代入の前に 暗黙的に呼び出される。その目的は、構造体オブジェクトが所有するリソースを解放することである。
struct S { int i; ~this() { import std.stdio; writeln("S(", i, ") is being destructed"); } } void main() { auto s1 = S(1); { auto s2 = S(2); // s2デストラクタが呼ばれた } S(3); // s3デストラクタが呼ばれた // s1デストラクタが呼ばれた }
構造体が別の構造体型のフィールドを持ち、そのフィールド自体がデストラクタを持つ場合、 そのデストラクタは親デストラクタの最後に呼び出される。親デストラクタが存在しない場合、 コンパイラがデストラクタを生成する。同様に、 デストラクタを持つ構造体の静的配列は、配列がスコープ外に出た際に各要素に対してデストラクタが呼び出される。
struct S { char c; ~this() { import std.stdio; writeln("S(", c, ") is being destructed"); } } struct Q { S a; S b; } void main() { Q q = Q(S('a'), S('b')); S[2] arr = [S('0'), S('1')]; // arr[1]、arr[0]、q.b、q.aに対してデストラクタが呼び出された }
構造体インスタンスのデストラクタは、 destroy。ただし、デストラクタは インスタンスがスコープ外に出た時点で再度呼び出されることに注意すること。
構造体のデストラクタはRAII で使用される。
共用体のフィールドの破棄
共用体はデストラクタを持つフィールドを持つことがある。しかし、共用体自体には決してデストラクタは存在しない。 共用体がスコープ外になると、そのフィールドのデストラクタは呼び出されない。 これらの呼び出しが必要な場合は、プログラマーが明示的に挿入しなければならない。
struct S { ~this() { import std.stdio; writeln("S is being destructed"); } } union U { S s; } void main() { import std.stdio; { writeln("entering first scope"); U u = U.init; scope (exit) writeln("exiting first scope"); } { writeln("entering second scope"); U u = U.init; scope (exit) { writeln("exiting second scope"); destroy(u.s); } } }
構造体不変
Invariant: invariant ( ) BlockStatement invariant BlockStatement invariant ( AssertArguments ) ;
構造体不変は、構造体インスタンスのメンバ間の関係を指定する。 これらの関係は、そのインスタンスとのあらゆるやりとりにおいて、その 公開インターフェースから
不変条件は、const メンバ関数の形式で記述する。不変条件は、 その不変条件内で実行されるすべてのAssertExpressionsが成功した場合に 満たされるように定義される。
struct Date { this(int d, int h) { day = d; // 日数は1..31 hour = h; // 時間は0..23 } invariant { assert(1 <= day && day <= 31); assert(0 <= hour && hour < 24); } private: int day; int hour; }
構造体には複数の不変条件が存在してもよい。それらは語彙順に適用される。
構造体不変条件は、構造体コンストラクタ(存在する場合)の終了時、および 構造体デストラクタ(存在する場合)の開始時に満たされなければならない。
構造体不変条件は、 すべてのパブリックまたはエクスポートされた非スタティックメンバ関数の入口と出口で保持されなければならない。 不変条件の適用順序は以下のとおりである。
- 前提条件
- 不変
- 関数本体
- 不変条件
- ポスト条件
構造体インスタンスがデフォルトの.init 値を使用して暗黙的に構築される場合、不変条件は満たされる必要はない。
不変条件が満たされない場合、プログラムは無効な状態になる。
- 構造体不変条件が実行時に実行されるかどうか。これは通常、 コンパイラのスイッチで制御される。
- 不変条件が満たされない場合の動作は、通常、 AssertExpressionsが失敗した場合と同じである。
パブリックまたはエクスポートされた非スタティックメンバ関数は、不変条件内で呼び出すことはできない。
struct Foo { public void f() { } private void g() { } invariant { f(); // エラー、invariantからパブリック・メンバ関数を呼び出すことはできない g(); // OK、g()はpublicではない } }
- 構造体インバリデーション内で、エクスポートされた関数やパブリック関数を間接的に呼び出すことはしないこと。 これは無限再帰を引き起こす可能性がある。
- 不変条件で副作用に依存することは避ける。不変条件が実行されるかされないか分からないため 。
- 不変条件を持つ構造体の変更可能なパブリックフィールドを持つことは避けるべきである。 そうすると、不変条件ではパブリックインターフェースを検証できなくなる。
同一性代入のオーバーロード
コピーコンストラクションでは、 同じ型のオブジェクトから別のオブジェクトを初期化するが、 代入はソースオブジェクトの内容をデスティネーションオブジェクトにコピーすることと定義され、 その過程でデスティネーションオブジェクトのデストラクタが呼び出される場合がある。
struct S { ... } // Sにはポストブリットまたはデストラクタがある S s; // sのデフォルト構造 S t = s; // tはsからコピー構築される t = s; // tはsから代入される
構造体代入t=s は、意味的には 以下と等価であると定義される。
t.opAssign(s);
ここで、opAssign は S のメンバ関数である。
ref S opAssign(ref S s) { S tmp = this; // これをtmpにビットコピーする this = s; // sをtmpにビットコピーする tmp.__dtor(); // tmpのデストラクタを呼び出す return this; }
構造体に対して、以下の条件の1つ以上が満たされる場合、同一代入のオーバーロードが必要となる。
- デストラクタを持つ
- ポストブライト関数を持つ
- 同一代入オーバーロードを持つフィールドを持つ
同一代入のオーバーロードが必要とされるが存在しない場合、 ref S opAssign(ref S) 型の同一代入オーバーロード関数が自動的に生成される。
ユーザー定義のものは同等のセマンティクスを実装できるが、 より効率的である可能性がある。
カスタムのopAssign がより効率的になる理由のひとつは、 "構造体"がローカルバッファへの参照を持っている場合である。
struct S { int[] buf; int a; ref S opAssign(ref const S s) return { a = s.a; return this; } this(this) { buf = buf.dup; } }
ここでは、S は一時的なワークスペースbuf[] を持っている。 通常の postblit は 無意味にそれを解放し、再割り当てする。カスタムのopAssignは 既存のストレージを再利用する。
エイリアス This
AliasThis: alias Identifier this ; alias this = Identifier ;
AliasThis宣言は、メンバをサブタイプに名前付けする。 識別子は、そのメンバを名前付けする。
構造体または共用体のインスタンスは、暗黙的にAliasThisメンバに変換できる。
struct S { int x; alias x this; } int foo(int i) { return i * 2; } void main() { S s; s.x = 7; int i = -s; assert(i == -7); i = s + 8; assert(i == 15); i = s + s; assert(i == 14); i = 9 + s; assert(i == 16); i = foo(s); // intへの暗黙の変換 assert(i == 14); }
メンバがクラスまたは構造体の場合、未定義の検索は AliasThisメンバに転送される。
class Foo { int baz = 4; int get() { return 7; } } struct Bar { Foo foo; alias foo this; } void main() { Bar bar = Bar(new Foo()); int i = bar.baz; assert(i == 4); i = bar.get(); assert(i == 7); }
Identifier がパラメータなしのプロパティ・メンバ関数を参照している場合、変換および未定義の 検索は関数の戻り値に転送される。
struct S { int x; @property int get() { return x * 2; } alias get this; } void main() { S s; s.x = 2; int i = s; assert(i == 4); }
構造体宣言でopCmp またはopEqualsメソッドを定義している場合、 それはAliasThis メンバーの定義よりも優先される。opCmp メソッドとは異なり、opEquals メソッドはstruct 宣言に対して暗黙的に定義される。 つまり、AliasThis メンバーの opEquals を使用する場合は、 明示的に定義する必要がある。
struct S { int a; bool opEquals(S rhs) const { return this.a == rhs.a; } } struct T { int b; S s; alias s this; } void main() { S s1, s2; T t1, t2; assert(s1 == s2); // S.opEqualsを呼び出す assert(t1 == t2); // メンバ単位の等式を実装するコンパイラが生成したT.opEqualsを呼び出す assert(s1 == t1); // s1.opEquals(t1.s);を呼び出す assert(t1 == s1); // t1.s.opEquals(s1);を呼び出す }
struct U { int a; bool opCmp(U rhs) const { return this.a < rhs.a; } } struct V { int b; U u; alias u this; } void main() { U u1, u2; V v1, v2; assert(!(u1 < u2)); // U.opCmpを呼び出す assert(!(v1 < v2)); // U.opCmpを呼び出す。VはopCmpメソッドを定義していないため、 // v1のエイリアスのthisが使用され; U.opCmpは // U型のパラメータを期待するため、v2のエイリアスのthisが使用される assert(!(u1 < v1)); // u1.opCmp(v1.u);を呼び出す assert(!(v1 < u1)); // v1.u.opCmp(v1);を呼び出す }
AliasThis については属性は無視される。
構造体/共用体には、AliasThisメンバを1つだけ含めることができる。
ネストされた構造体
構造体は、
- 関数のスコープ内で宣言されている場合、または
- ローカル関数のエイリアスとなる1つ以上のテンプレート引数を持つテンプレート化された構造体である 。
ネストされた構造体はメンバ関数を持つことができる。 隠しフィールドを介して、ネストされた構造体は、それを囲むスコープのコンテキストにアクセスできる。
void foo() { int i = 7; struct SS { int x,y; int bar() { return x + i + 1; } } SS s; s.x = 3; s.bar(); // 11を返す }
静的属性は、構造体がネストされることを防ぐ。そのため、 その構造体はその包含するスコープにアクセスできない。
void foo() { int i = 7; static struct SS { int x, y; int bar() { return i; // エラー、SSは入れ子構造体ではない } } }
共用体と特殊なメンバ関数
共用体には、ポストブリット、デストラクタ、または不変値を持たせることはできない。
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.109.1
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku