構造体, 共用体
クラスは参照型ですが、構造体は値型です。 Cの構造体はすべて、Dの構造体として正確に表現することができます。 C++ 用語で言うと、 Dの構造体は POD (Plain Old Data) 型、 すなわち"自明な"コンストラクタとデストラクタを持った型とも言えます。 構造体と共用体は、シンプルなデータの集約や、ハードウェアや外部の型の上に データ構造を定義する方法として使うことを意図されています。 外部の型とは、OSのAPIや、ファイルフォーマットなどによって定義される型です。 オブジェクト指向的な機能はクラスによって提供されます。
構造体には同一性(identity)の概念はありません。 つまり、 実装は必要ならいつでも構造体のビット単位のコピーを作って良いことになっています。
特徴 | 構造体 | クラス | C の構造体 | C++ の構造体 | C++ のクラス |
---|---|---|---|---|---|
値型 | X | X | X | X | |
参照型 | X | ||||
データメンバ | X | X | X | X | X |
隠れメンバ | X | X | X | X | |
静的メンバ | X | X | X | X | |
デフォルト初期化子 | X | X | |||
ビットフィールド | X | X | X | ||
非仮想メンバ関数 | X | X | X | X | |
仮想メンバ関数 | X | X | X | ||
コンストラクタ | X | X | X | X | |
postblit/copy コンストラクタ | X | X | X | ||
デストラクタ | X | X | X | X | |
shared静的コンストラクタ | X | X | |||
shared静的デストラクタ | X | X | |||
RAII | X | X | X | X | |
代入のオーバーロード | X | X | X | ||
リテラル | X | ||||
演算子オーバーロード | X | X | X | X | |
継承 | X | X | X | ||
不変条件 | X | X | |||
単体テスト | X | X | |||
同期処理 | X | ||||
パラメタ化 | X | X | X | X | |
アラインメント制御 | X | X | |||
メンバのアクセス制御 | X | X | X | X | |
デフォルトでpublic | X | X | X | X | |
タグ名前空間 | X | X | X | ||
無名構造体/クラス | X | X | X | X | |
静的コンストラクタ | X | X | |||
静的デストラクタ | X | X | |||
const/immutable/shared | X | X | |||
内部ネスト | YES | YES |
AggregateDeclaration:
struct Identifier StructBody
union Identifier StructBody
struct Identifier ;
union Identifier ;
StructTemplateDeclaration
UnionTemplateDeclaration
StructBody:
{ }
{ StructBodyDeclarations }
StructBodyDeclarations:
StructBodyDeclaration
StructBodyDeclaration StructBodyDeclarations
StructBodyDeclaration:
DeclDef
StructAllocator
StructDeallocator
StructPostblit
AliasThis
StructAllocator:
ClassAllocator
StructDeallocator:
ClassDeallocator
Cの場合と同様に動きます。ただし、以下の例外を除きます:
- ビットフィールドはありません
- アラインメントを明示的に指定できます
- タグ名の名前空間は分離されていません - タグ名は現在のスコープに入ります
- 次のような宣言:
struct ABC x;
は不正です。次のように書きます:ABC x;
- 他の構造体/共用体のメンバとして、無名構造体/共用体が使えます
- メンバのデフォルト初期化子を指定できます
- メンバ関数やstaticメンバを作れます
不透明構造体と共用体
StructBody を持たない構造体/共用体宣言を不透明(opaque)であると言います:
struct S;
union U;
メンバは完全にユーザーから隠されるため、 可能な操作はこれらの型の内容に関する知識がゼロでも実行できるものに限ります。 例えば:
struct S;
S.sizeof; // エラー。サイズはわからない
S s; // エラー。不明な内容の初期化はできない
S* p; // OK。内容の知識はいらない
これらは pimpl イディオム の実装などに使えます。
構造体の静的初期化
静的構造体メンバは、 メンバに対して指定されたデフォルト初期化値へと初期化されます。 指定がなかった場合は、そのメンバの型のデフォルト初期化値が使用されます。 もし静的初期化子が指定されていれば、メンバは メンバ名 コロン 式、 の三つ組みで初期化されます。メンバ初期化の順序は問われません。 静的メンバの初期化子はコンパイル時に評価可能なものに限られます 初期化リストに指定されていないメンバは、 デフォルト値で初期化されます。struct S { int a; int b; int c; int d = 7;}
static S x = { a:1, b:2}; // c は 0, d は 7
static S z = { c:4, b:5, a:2 , d:5}; // z.a = 2, z.b = 5, z.c = 4, z.d = 5
C方式の、
メンバの順序に基づいた初期化もサポートされています:
static S q = { 1, 2 }; // q.a = 1, q.b = 2, q.c = 0, q.d = 7
static変数の初期化には構造体リテラルも使用可能ですが、 コンパイル時評価可能なものに限られます。
static S q = S( 1, 2+3 ); // q.a = 1, q.b = 5, q.c = 0, q.d = 7
static初期化子の構文は、メンバ名を指定しない形式に限り、 非static変数の初期化にも使用可能です。 この場合はコンパイル時評価の不可能な式を指定することもできます。
void test(int i) {
S q = { 1, i }; // q.a = 1, q.b = i, q.c = 0, q.d = 7
}
共用体の静的初期化
共用体は明示的に初期化します。union U { int a; double b; }
static U u = { b : 5.0 }; // u.b = 5.0
初期化子と重なるけれどもより大きなメモリを使うメンバがある場合、
その部分は、
0初期化されます。
構造体の動的初期化
構造体は、 同じ型の別の値を使って動的に初期化できます:
struct S { int a; }
S t; // デフォルト初期化
t.a = 3;
S s = t; // 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)
{ S s;
s.a = v.a + 1;
return s;
}
}
S s = 3; // s.a は 3 になる
S t = s; // t.a は 3 になる。 S.opCall(s) は呼ばれない
構造体リテラル
構造体リテラルは、 構造体名のあとに括弧で引数リストを続けた物です:
struct S { int x; float y; }
int foo(S s) { return s.x; }
foo( S(1, 2) ); // フィールド x を 1 に、フィールド y を 2 にセット。
構造体リテラルは、構文的には関数呼び出しのように見えます。 構造体がメンバ関数 opCall を実装していた場合は、 その構造体のリテラルは使えません。 フィールド数より多い引数を書くと エラーです。 フィールド数より引数の数が少ない場合は、 残りのフィールドは デフォルト初期化されます。 構造体のメンバに無名unionが会った場合、 構造体リテラルで初期化できるのは1つめのunionのメンバのみです。 残りの、 重なっていない部分はデフォルト初期化されます。
構造体のプロパティ
.sizeof | 構造体のbyte単位でのサイズ |
.alignof | 構造体が整列されなければならないバイト境界の値 |
.tupleof | フィールドのタプルを取得 |
構造体フィールドのプロパティ
.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 は const
t.a = 4; // エラー。t.a は const
}
構造体コンストラクタ
構造体コンストラクタは、 構造体のインスタンスの初期化のために使用されます。 ParameterList を空にすることはできません。 コンストラクタ以外の方法でインスタンス化された構造体インスタンスは、 デフォルト値 .init で初期化されます。
struct S {
int x, y;
this() // エラー。構造体のデフォルトコンストラクタは定義できない
{
}
this(int a, int b)
{
x = a;
y = b;
}
}
void main()
{
S a = S(4, 5);
auto b = S(); // auto b = S.init; と同じ
}
構造体 Postblits
StructPostblit:
this(this) FunctionBody
コピーコンストラクト とは、 構造体のインスタンスを同じ型の別のインスタンスによって初期化することと定義されます。 コピーコンストラクトには二つの段階で構成されます:
- フィールドのblit。つまり、ビット毎のコピー
- その結果に対し postblit を実行
一段階目は言語によって自動的に処理され、 二段階目は、構造体に postblit 関数が定義されていた場合に実行されます。 postblit は、コピー先の構造体オブジェクトに対してのみアクセス可能で、 コピー元を触ることはできません。 ここで行われるべき処理は、 必要に応じて参照型データをコピーしたり参照カウントを増やすなどの、 コピー先の ‘fix up’ 処理です。例:
struct S {
int[] a; // この配列はインスタンス毎にprivateに所有させたい
this(this) {
a = a.dup;
}
~this() {
delete a;
}
}
共用体に postblit が必要なフィールドを持たせることはできません。
構造体デストラクタ
デストラクタは、オブジェクトがスコープを外れたときに呼び出されます。 構造体オブジェクトの所有するリソースの解放などが 主な目的です。
構造体にデストラクタのあるフィールドを持たせることはできません。
代入のオーバーロード
コピーコンストラクトはオブジェクトを別のオブジェクトによって 初期化する際の作業のことをいいますが、 代入は、別のオブジェクトの内容を他の既に初期化済みのオブジェクトにコピーすること、 と定義されています:
struct S { ... }
S s; // s のデフォルト初期化
S t = s; // t は s からコピーコンストラクトされる
t = s; // t は s から代入される
構造体代入 t=s は、 意味的に以下と同等に解釈されます:
t.opAssign(s);
デフォルトの opAssign は概念的に以下のような処理をする S のメンバ関数です:
S* opAssign(ref const S s)
{ ... *this を tmp にビットコピー ...
... s を *this にビットコピー ...
... tmp のデストラクタ呼び出し ...
return this;
}
必要に応じてコンパイラはデフォルトの opAssign を生成しますが、 これをユーザー定義することも可能です。 ユーザー定義の際も同じ意味論を持った定義しなければなりませんが、 より効率のよい実装を提供することは可能です。
ユーザー定義 opAssign の方が効率的になる場合としては、 以下のように構造体がローカルバッファへの参照を保持している場合などがあります:
struct S {
int[] buf;
int a;
S* opAssign(ref const S s) {
a = s.a;
return this;
}
this(this) {
buf = buf.dup;
}
~this() {
delete buf;
}
}
S はテンポラリの作業領域 buf[]> をメンバに持っています。 通常の postblit は無駄にこれをfreeし再割り当てしますが、カスタムの opAssign では既存の領域を再利用することができます。
ネスト構造体
A ネスト構造体 とは、 関数スコープの内側で定義された構造体と、 ローカル関数をalias引数にとるテンプレート構造体のことを言います。 ネスト構造体はメンバ関数を持つことができ、 (追加された隠しフィールドを通して) 囲むスコープのコンテキストにアクセス可能です。
void foo() {
int i = 7;
struct SS {
int x,y;
int bar() { return x + i + 1; }
}
SS s;
s.x = 3;
s.bar(); // 11 を返す
}
static 属性を明示することで、 構造体がネスト構造体となることを阻止できます。 当然、 その場合は囲むスコープの変数にはアクセスできません。
void foo() {
int i = 7;
static struct SS {
int x,y;
int bar() {
return i; // エラー。SS はネスト構造体ではない
}
}
}
ローカル関数をalias引数としてとる構造体テンプレートは、 ネスト構造体になります。
struct A(alias F) {
int fun(int i) { return F(i); }
}
A!(F) makeA(alias F)() {return A!(F)(); }
void main() {
int x = 40;
int fun(int i) { return x + i; }
A!(fun) a = makeA!(fun)();
a.fun(2);
}