D 1.0   D 2.0
About Japanese Translation

www.digitalmars.com
Last update Fri Oct 3 12:57:03 2008

構造体と共用体

クラスは参照型ですが、構造体は値型です。 Cの構造体はすべて、Dの構造体として正確に表現することができます。 C++ 用語で言うと、 Dの構造体は POD (Plain Old Data) 型、 すなわち"自明な"コンストラクタとデストラクタを持った型とも言えます。 構造体と共用体は、シンプルなデータの集約や、ハードウェアや外部の型の上に データ構造を定義する方法として使うことを意図されています。 外部の型とは、OSのAPIや、ファイルフォーマットなどによって定義される型です。 オブジェクト指向的な機能はクラスによって提供されます。

構造体には同一性(identity)の概念はありません。 つまり、 実装は必要ならいつでも構造体のビット単位のコピーを作って良いことになっています。

構造体とクラスの対比
Feature 構造体 クラス 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
postblit/コピーコンストラクタ X   X   X
デストラクタ X X   X X
RAII   X   X X
代入オーバーロード X     X X
リテラル X        
演算子オーバーロード X X   X X
継承   X   X X
不変条件 X X      
単体テスト X X      
synchronized   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/invariant X X      
AggregateDeclaration:
	Tag Identifier StructBody
	Tag Identifier ;

Tag:
	struct
	union

StructBody:
	{ }
	{ StructBodyDeclarations }

StructBodyDeclarations:
	StructBodyDeclaration
	StructBodyDeclaration StructBodyDeclarations

StructBodyDeclaration:
	Declaration
	StaticConstructor
	StaticDestructor
	Invariant
	UnitTest
	StructAllocator
	StructDeallocator
	StructConstructor
	StructPostblit
	StructDestructor

StructAllocator:
	ClassAllocator

StructDeallocator:
	ClassDeallocator

Cの場合と同様に動きます。ただし、以下の例外を除きます:

構造体の静的初期化

静的構造体メンバは、 メンバに対して指定されたデフォルト初期化値へと初期化されます。 指定がなかった場合は、そのメンバの型のデフォルト初期化値が使用されます。 もし静的初期化子が指定されていれば、メンバは メンバ名 コロン 式、 の三つ組みで初期化されます。メンバ初期化の順序は問われません。 静的メンバの初期化子はコンパイル時に評価可能なものに限られます 初期化リストに指定されていないメンバは、 デフォルト値で初期化されます。
struct X { int a; int b; int c; int d = 7;}
static X x = { a:1, b:2};	      // c は 0, d は 7
static X z = { c:4, b:5, a:2 , d:5};  // z.a = 2, z.b = 5, z.c = 4, z.d = 5
C方式の、 メンバの順序に基づいた初期化もサポートされています:
static X q = { 1, 2 };	  // q.a = 1, q.b = 2, q.c = 0, q.d = 7

static変数の初期化には構造体リテラルも使用可能ですが、 コンパイル時評価可能なものに限られます。

static X q = S( 1, 2+3 );   // q.a = 1, q.b = 5, q.c = 0, q.d = 7

static初期化子の構文は、メンバ名を指定しない形式に限り、 非static変数の初期化にも使用可能です。 この場合はコンパイル時評価の不可能な式を指定することもできます。

void test(int i)
{
    S s = { 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/invariant 構造体

構造体の宣言を記憶域クラス constinvariant で修飾することができます。 これは、すべてのメンバを const / invariant で修飾したのと同じ効果があります。

const struct S { int a; int b = 2; }

void main()
{
    S s = S(3);    // s.a を 3 に初期化
    S t;           // t.a を 0 に初期化
    t = s;         // ok。t.a は 3
    t.a = 4;       // エラー。t.a は const
}

構造体コンストラクタ

StructConstructor:
    this( ParameterList ) FunctionBody

構造体コンストラクタは、 構造体のインスタンスの初期化のために使用されます。 ParameterList を空にすることはできません。 コンストラクタ以外の方法でインスタンス化された構造体インスタンスは、 デフォルト値 .init で初期化されます。

構造体 Postblit

StructPostblit:
    this(this) FunctionBody

コピーコンストラクト とは、 構造体のインスタンスを同じ型の別のインスタンスによって初期化することと定義されます。 コピーコンストラクトには二つの段階で構成されます:

  1. フィールドのblit。つまり、ビット毎のコピー
  2. その結果に対し postblit を実行

一段階目は言語によって自動的に処理され、 二段階目は、構造体に postblit 関数が定義されていた場合に実行されます。 postblit は、コピー先の構造体オブジェクトに対してのみアクセス可能で、 コピー元を触ることはできません。 ここで行われるべき処理は、 必要に応じて参照型データをコピーしたり参照カウントを増やすなどの、 コピー先の 'fix up' 処理です。例:

struct S
{
   int[] a;    // この配列はインスタンス毎にprivateに所有させたい
   this(this)
   {
       a = a.dup;
   }
   ~this()
   {
       delete a;
   }
}

構造体デストラクタ

StructDestructor:
    ~this() FunctionBody

デストラクタは、オブジェクトがスコープを外れたときに呼び出されます。 構造体オブジェクトの所有するリソースの解放などが 主な目的です。

代入オーバーロード

コピーコンストラクトはオブジェクトを別のオブジェクトによって 初期化する際の作業のことをいいますが、 代入は、別のオブジェクトの内容を他の既に初期化済みのオブジェクトにコピーすること、 と定義されています:

struct S { ... }
S s;      // s のデフォルト初期化
S t = s;  // t は s からコピーコンストラクトされる
t = s;    // t は s から代入される

構造体代入 t=s は、 意味的に以下と同等に解釈されます:

t = S.opAssign(s);

デフォルトの opAssign は概念的に以下のような処理をする S のメンバ関数です:

S* opAssign(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 では既存の領域を再利用することができます。