クラス
D のオブジェクト指向的な機能は、 全てクラスで実現されています。 クラス階層のルートにはObjectクラスがあります。Objectは、 それぞれの派生クラスが最低限持つ機能と、そのデフォルト実装を定義しています。
クラスは、プログラマ定義の型です。 クラスのサポートが、 カプセル化、継承、多態など、D をオブジェクト指向言語たらしめています。 D のクラスは、単一継承と、それに加えてインターフェイス継承が可能です。 クラスオブジェクトは参照によってのみインスタンス化されます。
クラスはexportできます。 つまり、DLLやEXEから外部に、 クラス名やその非privateメンバを公開することができます。
クラス宣言の定義:
ClassDeclaration: class Identifier BaseClassList ClassBody ClassTemplateDeclaration BaseClassList: Empty : SuperClass : SuperClass , InterfaceClasses : InterfaceClass SuperClass: Identifier Protection Identifier InterfaceClasses: InterfaceClass InterfaceClass , InterfaceClasses InterfaceClass: Identifier Protection Identifier Protection: private package public export ClassBody: { } { ClassBodyDeclarations } ClassBodyDeclarations: ClassBodyDeclaration ClassBodyDeclaration ClassBodyDeclarations ClassBodyDeclaration: DeclDef Invariant ClassAllocator ClassDeallocatorクラスは、以下の構成要素からなります:
- 基底クラス
- インターフェイス
- 動的フィールド
- 静的フィールド
- 型
- メンバ関数
class Foo
{
... メンバ ...
}
クラス定義の閉じ括弧 } の後ろに ;
が無いことにご注意下さい。
また、変数を次のように宣言することもできません:
class Foo { } var;
代わりにこう書きます:
class Foo { }
Foo var;
フィールド
クラスのメンバには常に . 演算子でアクセスします。 C++のような :: や -> 演算子はありません。
D コンパイラは、 クラスのフィールドの順序を自由に並び替えて、 実装定義の方法で最適なメモリ配置とすることができます。 フィールドは、 関数におけるローカル変数のようなものと考えて下さい。 ローカル変数は、 最適なフレームレイアウトのために、 コンパイラが自由に並び替えてスタックやレジスタを割り当てます。 これによってコードの設計者は、 マシンに最適な順に従うのではなく、 読みやすさを重視してフィールドを並べることができます。 明示的なメモリレイアウトの制御は、 クラスではなく構造体/共用体で行います。
フィールドのプロパティ
.offsetof プロパティは、フィールドの、 クラスの実体の先頭から数えたバイト数を返します。 .offsetof はフィールドそのものを表す式に対してのみ適用できます。 クラスの型で修飾されたフィールドには使えません:
class Foo { int x; } ... void test(Foo foo) { size_t o; o = Foo.x.offsetof; // エラー。Foo.x には 'this' が必要 o = foo.x.offsetof; // ok }
クラスのプロパティ
.tupleof プロパティは、 隠しフィールドおよび基底クラスのフィールドを除く、 全てのフィールドからなるタプルを返します。
class Foo { int x; long y; } void test(Foo foo) { foo.tupleof[0] = 1; // foo.x を 1 に foo.tupleof[1] = 2; // foo.y を 2 に foreach (x; foo.tupleof) writef(x); // 12 を表示 }
プロパティ .__vptr と .__monitor は、それぞれ、 クラスオブジェクトの vtbl[] とモニタを指しています。 ただし、これらはユーザーコードで使用すべきではありません。
基底クラス
全てのクラスは、1つの基底クラスを継承します。特に指定がなければ Object クラスからの継承になります。Object は、 Dのクラス継承階層のルートです。仮想関数
staticでないメンバ関数は、 隠し引数 this をもち、 この引数を通して他のクラスメンバにアクセスできます。
同期関数
同期クラスメンバ関数は、記憶域クラス synchronized で宣言されます。 静的メンバ関数の場合、そのクラスの classinfo オブジェクトによって同期が行われます。つまり、 そのクラスの全ての静的メンバ関数はすべておなじ一つのモニタを使用します。 非静的メンバ関数の場合、 使用されるモニタはオブジェクトごとに用意されます。例えば:
class Foo { synchronized void bar() { ...statements... } }
は、以下と同等です:
class Foo { void bar() { synchronized (this) { ...statements... } } }
構造体には同期メンバ関数はありません。
コンストラクタ
Constructor: this Parameters FunctionBody
メンバは、 常にその型の デフォルト初期化子で初期化されます。 整数型なら 0、浮動小数点数型なら NaN、 といった具合です。 これによって、 あるコンストラクタでメンバの初期化を忘れたために 隠れた問題が生じる、という事態を避けることができます。 クラスの定義では、 デフォルトを置き換える静的初期化子を指定できます:
class Abc { int a; // デフォルトの初期化子は 0 long b = 7; // b のデフォルトの初期化子は 7 float f; // f のデフォルトの初期化子は NaN }静的な初期化は、 コンストラクタ呼び出しより前に実行されます。
コンストラクタは返値を持たない、 this という名前の関数です:
class Foo { this(int x) // Foo のコンストラクタを宣言 { ... } this() { ... } }基底クラスの構築は、基底のコンストラクタを super という名前で呼び出して行います:
class A { this(int y) { } } class B : A { int j; this() { ... super(3); // 基底クラスのコンストラクタ A.this(3) を呼び出し ... } }
コンストラクタは、同じクラスの他のコンストラクタを呼ぶことで 初期化処理を共有できます。 (移譲コンストラクタ (delegating constructor) と呼びます):
class C { int j; this() { ... } this(int i) { this(); j = i; } }もし this や super による他のコンストラクタの呼び出しが コンストラクタ中に存在せず、基底クラスがコンストラクタを持っていれば、 コンストラクタの先頭に super() の呼び出しが自動で挿入されます。
クラスにコンストラクタが定義されていないが、基底クラスにはある、 という場合は、次の形式のデフォルトコンストラクタ:
this() { }
が暗黙のうちに生成されます。
クラスオブジェクトの構築は非常に柔軟性がありますが、 いくつかの制限も加わります:
- お互いを呼び出し合うコンストラクタは不正です
this() { this(1); } this(int i) { this(); } // 不正, コンストラクタの循環呼び出し
- コンストラクタ内に別のコンストラクタ呼び出しがあるときは、
どの実行パスを通ってもちょうど1つだけコンストラクタが呼ばれるように
なっていなければなりません:
this() { a || super(); } // 不正 this() { a ? this(1) : super(); } // ok this() { for (...) { super(); // 不正, ループの内側 } }
- コンストラクタ呼び出しより前に、暗黙にも明示的にも this を参照することはできません。
- コンストラクタ呼び出しはラベルの後に置くことはできません。 (gotoが存在するときに、上に書いた条件のチェックを簡単にするためです。)
クラスオブジェクトのインスタンスは、NewExpression で生成します。
A a = new A(3);
以下のステップが実行されます:
- オブジェクト用のメモリ領域を割り当てます。 失敗すると、null を返すのではなく OutOfMemoryException 例外が投げられます。 従って、面倒なnullチェックは不要です。
- クラス定義で与えられた値で、 データを静的に初期化します。 ここでvtbl[] (仮想関数へのポインタのテーブル)へのポインタが設定されます。 これによって、 コンストラクタの時点で、 完全に構築されて仮想関数も呼び出せるオブジェクトが渡されることを保証します。 これは静的な初期オブジェクトを新しいメモリ領域へ memcpy する操作と同等ですので、 高性能なコンパイラはそのような最適化を行うかもしれません。
- もしクラスにコンストラクタが定義されていれば、 引数リストに合うコンストラクタが呼び出されます。
- クラス不変条件チェックがONなら、 コンストラクタの後に invariant が呼び出されます。
デストラクタ
Destructor: ~this() FunctionBodyオブジェクトが削除される際に、 ガベージコレクタがデストラクタ関数を呼び出します。 構文は:
class Foo { ~this() // Foo のデストラクタ { } }
一つのクラスにつき高々一つのデストラクタしか存在できません。 デストラクタは引数を取らず、属性を何も持ちません。 常に仮想関数です。
デストラクタは、 オブジェクトの保持するリソースを解放する役割を担います。
プログラムは、オブジェクトがもはや参照されないことを(delete式によって) ガベージコレクタへ明示的に伝えることもできます。 この場合はGCはすぐに デストラクタを呼び出し、オブジェクトのメモリを自由領域へ戻します。 デストラクタが2回以上呼ばれないことは保証されています。
デストラクタの終了後には、 自動的に基底クラスのデストラクタが呼び出されます。 基底クラスのデストラクタを明示的に呼ぶ方法は存在しません。
GC が参照されなくなったオブジェクトを必ず解放するという保証はありません。 さらに、 GC がデストラクタを呼び出す順番も保証されません。 これはつまり、 GC管理下にあるオブジェクトへの参照をメンバとして持つオブジェクトのデストラクタを ガベージコレクタが呼び出すときには、それらの参照は既に無効となっている可能性があるということです。 従って、 デストラクタからはメンバオブジェクトを参照できません。 この規則は、 autoオブジェクトや DeleteExpression で削除されるオブジェクト (GCが自動で解放することはないので、参照は必ず有効)には当てはまりません。
データセグメントから参照されているオブジェクトは、 決してGCに回収されることはありません。
静的コンストラクタ
StaticConstructor: static this() FunctionBody静的コンストラクタは、 main() に制御が移る前に初期化を実行する関数として 定義されています。静的コンストラクタは、 クラスの静的メンバを、 コンパイル時には計算できない値で初期化する際に使用します。
他の言語では、 コンパイル時に計算できない値をメンバの初期化子として使うことで、 暗黙の内に組み込まれます。この方法に関する問題は、 いつコードが実行されるのか正確な制御をすることでくい止めることができます。 例えば:
class Foo { static int a = b + 1; static int b = a * 2; }aとbは結局どの値になるのか? 初期化が実行される順序は? 初期化が始まる前のaとbの値は何なのか、 これはコンパイルエラーになるのか、 それとも 実行時エラーになるのか? 様々な混乱は、初期化子が静的なのか動的なのか明確でないことが原因となっています。
D はこれを単純にします。 全てのメンバ初期化は、 コンパイラによってコンパイル時に決定されなくてはなりません。 従って、 メンバの初期化には評価順序の依存関係はなく、 初期化されていない値を読みとることはできなくなっています。 動的な初期化は、特別な構文 static this() で定義される 静的コンストラクタによって行われます。
class Foo { static int a; // デフォルトの 0 に初期化される static int b = 1; static int c = b + a; // エラー, 初期化子が定数でない static this() // 静的コンストラクタ { a = b + 1; // a を 2 に設定 b = a * 2; // b を 4 に設定 } }static this() は起動時に、 main() の呼び出し前に呼ばれます。 正常終了すれば(例外を投げなければ)、 静的デストラクタが、 プログラム終了時に呼び出す関数のリストへ追加されます。 静的コンストラクタは空の引数リストを持ちます。
同一モジュール内の複数の静的コンストラクタは、 ソース中に登場する順に上から実行されます。 他のモジュールからimportされているモジュールの静的コンストラクタは、 全て、 importする側の静的コンストラクタよりも前に実行されます。
静的コンストラクタ宣言の static は、属性ではありません。 必ず this の直前に書く必要があります:
class Foo { static this() { ... } // 静的コンストラクタ static private this() { ... } // 静的コンストラクタではない static { this() { ... } // 静的コンストラクタではない } static: this() { ... } // 静的コンストラクタではない }
静的デストラクタ
StaticDestructor: static ~this() FunctionBody静的デストラクタは、static ~this() という構文で定義される特殊な関数です。
class Foo { static ~this() // 静的デストラクタ { } }静的デストラクタは、 対応する静的コンストラクタが正常に実行完了しているときのみ、 プログラムの終了時に呼び出されます。 静的デストラクタは空の引数リストを持ちます。 静的デストラクタは、 静的コンストラクタの呼び出しと逆順で実行されます。
静的デストラクタの宣言の static は、属性ではありません。 必ず ~this の直前に書く必要があります:
class Foo { static ~this() { ... } // 静的デストラクタ static private ~this() { ... } // 静的デストラクタではない static { ~this() { ... } // 静的デストラクタではない } static: ~this() { ... } // 静的デストラクタではない }
クラス不変条件
Invariant: invariant() BlockStatementクラス不変条件は、 (メンバ関数の実行途中以外は)常に真でなければならない クラスの性質を記述するために使われます。例えば、 日時を表現するクラスは、"日"が1..31で"時"が0..23である、という不変条件を持ちます:
class Date { int day; int hour; invariant() { assert(1 <= day && day <= 31); assert(0 <= hour && hour < 24); } }
クラス不変条件は、 assertがtrueでなければならない、という契約です。 不変条件は、コンストラクタ完了直後、 デストラクタ実行直前、 そしてpublic/export メンバ関数の実行前後にチェックされます。
invariant内では、直接間接を問わず、 そのクラスのstaticでないpublicメンバを 呼び出すことができません。 そのような呼び出し方をすると 再帰的に invariant のチェックが入ってしまい、スタックオーバーフローが発生します。
invariant は public や export メンバの開始時に呼び出されるため、 そのようなメンバ関数は コンストラクタからは呼び出すべきではありません。
class Foo { public void f() { } private void g() { } invariant() { f(); // エラー。invariantの中でpublicメンバ関数は呼べない g(); // OK。 g() はpublicではない。 } }
assert()
式の引数にクラスオブジェクトを渡したときも、
不変条件がチェックできます:
Date mydate; ... assert(mydate); // Dateクラスの不変条件が成立していることのチェック不変条件が破れていた場合、 AssertError 例外が投げられます。 不変条件は継承されます。 つまり、全てのクラスの不変条件には、 暗黙の内に基底クラスの不変条件が追加されます。
クラス1つにつき、Invariant は最大一つしか定義できません。
リリース用にコンパイルする際には不変条件のコードは生成されず、 プログラムは最高速で動作します。
クラス・アロケータ
ClassAllocator: new Parameters FunctionBody次の形をしたメンバ関数:
new(uint size) { ... }は、クラス・アロケータと呼ばれます。 クラスアロケータは、第一引数の型がuintでさえあれば、 任意個数の引数を取ることができます。 1つのクラスに幾つでも定義することができ、 通常の関数オーバーロードの規則に従って正しいものが呼び出されます。 new式:
new Foo;
が実行されて、
Fooがアロケータを持つクラスであれば、
インスタンスがメモリ上で占めるサイズを第一引数として、
アロケータが呼び出されます。
アロケータはメモリを割り当て、
void* で返さなくてはなりません。
もしアロケータが失敗したら、
null を返すのではなく例外を投げるようします。
アロケータが2個以上の引数を取るのであれば、
NewExpression の
new の後に、括弧で追加の引数を指定できます:
class Foo { this(char[] a) { ... } new(uint size, int x, int y) { ... } } ... new(1,2) Foo(a); // new(Foo.sizeof,1,2)の呼び出し
派生クラスは、 特に指定が無ければ親クラスからアロケータを継承します。
インスタンスがスタック上に割り当てられるときには、 アロケータは呼ばれません。
クラス・デアロケータ
ClassDeallocator: delete Parameters FunctionBody次の形をしたメンバ関数:
delete(void *p) { ... }は、クラス・デアロケータと呼ばれます。 デアロケータは void* 型の引数を唯一つ取ります。 1クラスにつき1つだけ指定できます。 delete式:
delete f;
が実行されて、 fがデアロケータを持つクラスのインスタンスへの参照であれば、 (存在すれば)デストラクタが呼ばれた後、 インスタンスへのポインタを引数に、 デアロケータが呼ばれます。メモリを解放するのは、デアロケータの責任です。
派生クラスは、 特に指定が無ければ親クラスからデアロケータを継承します。
インスタンスがスタック上に割り当てられているときには、 アロケータは呼ばれません。
scope クラス
次のようにscope属性の付いたクラスが、scopeクラスです:scope class Foo { ... }scope性は継承され、 scopeクラスから派生したクラスは皆scopeになります。
scopeクラスの参照は、関数のローカル変数としてのみ存在できます。 その変数は scope と宣言されていなくてはいけません:
scope class Foo { ... } void func() { Foo f; // エラー、scopeクラスの参照はscopeでなくてはダメ scope Foo g = new Foo(); // 正しい }scopeクラスの参照がスコープから抜けると、(もし存在すれば) デストラクタが自動的に呼ばれます。これは、 例外によってスコープを抜けた時であっても成り立ちます。
final クラス
final と宣言されたクラスは継承できません:
final class A { } class B : A { } // エラー。クラス A は final
ネストクラス
ネストクラス とは、 関数や他のクラスのスコープ内で定義されるクラスのことをいいます。 ネストクラスからは、すぐ外側のクラスと関数内で定義された変数や その他のシンボルにアクセスすることができます:class Outer { int m; class Inner { int foo() { return m; // Outerのメンバへアクセスできる } } } void func() { int m; class Inner { int foo() { return m; // func() のローカル変数にアクセスできる } } }ネストクラスが static 属性付きで宣言されていた場合、 スタックローカルの変数や this の必要なシンボルなどの周囲のスコープにはアクセスできなくなります:
class Outer { int m; static int n; static class Inner { int foo() { return m; // エラー。Innerはstaticでmにはthisが必要 return n; // OK。n は static } } } void func() { int m; static int n; static class Inner { int foo() { return m; // エラー。Innerはstaticでmはスタックローカル return n; // OK。n は static } } }非staticネストクラスは、隠しメンバ(コンテクスト・ポインタと呼びます) を一つ加えることで実現されています。 関数内にあるときは周囲の関数のフレームポインタが、 クラス内にあるときは周囲のクラスの this ポインタが、隠しメンバに格納されています。
非staticネストクラスがインスタンス化される際には、 コンストラクタの呼び出し前にコンテキスト・ポインタが設定されるので、 コンストラクタからは周辺の変数へと完全にアクセス可能です。 非staticネストクラスは、 必要なコンテキスト・ポインタ情報が入手可能な時のみインスタンス化できます:
class Outer { class Inner { } static class SInner { } } void func() { class Nested { } Outer o = new Outer; // Ok Outer.Inner oi = new Outer.Inner; // エラー。Outerに'this'がない Outer.SInner os = new Outer.SInner; // Ok Nested n = new Nested; // Ok }非staticなネストクラスは周囲の関数のスタック変数へとアクセスできるのですが、 その関数が終了した後は、 アクセスは不正となります:
class Base { int foo() { return 1; } } Base func() { int m = 3; class Nested : Base { int foo() { return m; } } Base b = new Nested; assert(b.foo() == 3); // Ok, func() はまだ実行中 return b; } int test() { Base b = func(); return b.foo(); // Error, func().m は未定義 }このような機能が必要な場合は、 ネストクラスのコンストラクタ内で、 必要な変数のコピーを作成します:
class Base { int foo() { return 1; } } Base func() { int m = 3; class Nested : Base { int m_; this() { m_ = m; } int foo() { return m_; } } Base b = new Nested; assert(b.foo() == 3); // Ok, func() はまだ実行中 return b; } int test() { Base b = func(); return b.foo(); // Ok, func().m のキャッシュされたコピーを使用 }
NewExpressionの頭にくっつけることで、 内部クラスの生成時に this を指定することができます:
class Outer { int a; class Inner { int foo() { return a; } } } int bar() { Outer o = new Outer; o.a = 3; Outer.Inner oi = o.new Inner; return oi.foo(); // 3を返す }
この例では、o が外部クラス Outer のインスタンスを指す this として機能しています。
内部クラスのインスタンスのプロパティ .outer は、 外部クラスの this ポインタを返します。外部コンテキストがクラスでない場合 (※訳注:関数内クラスなど)、.outer は void* でコンテキストへのポインタを返します。
class Outer { class Inner { Outer foo() { return this.outer; } } void bar() { Inner i = new Inner; assert(this == i.foo()); } } void test() { Outer o = new Outer; o.bar(); }
無名ネストクラス
無名ネストクラスは、 NewAnonClassExpression で定義とインスタンス化が同時に行われます:
NewAnonClassExpression: new ParenArgumentListopt class ParenArgumentListopt SuperClassopt InterfaceClassesopt ClassBody ParenArgumentList: ( ArgumentList )
これは次と同等です:
class Identifier : SuperClass InterfaceClasses ClassBody new (ArgumentList) Identifier (ArgumentList);
Identifier は無名ネストクラス用に生成されたクラス名とします。