D 2.0
About Japanese Translation

www.digitalmars.com
Last update Mon Dec 31 18:42:36 2007

Const と Invariant

データ構造やインターフェイスを調べるときには、 どのデータが不変でどのデータが可変で、変更を行う可能性があるのは誰か、 といった情報が簡単にわかると非常に便利です。 そしてこれは、言語の型システムの助けによって実現することができます。 データは const あるいは invariant とマーク付けすることができ、 デフォルトでは変更可能 (mutable) となっています。

invariant は、 一度構築されたら決して変更されることのないデータに適用します。 invariant なデータ値は、 プログラムの実行中一貫して同じ値であり続けます。 invariant なデータは ROM (Read Only Memory) や、ハードウェアによって読み取り専用とされたメモリページに配置することができます。 invariant なデータは決して変更されないので、 さまざまなプログラム最適化の機会をもたらします。また、 アプリケーションをより関数型のプログラミングスタイルで書くことを可能にします。

const は、そのconst参照を通しては変更を加えることのないデータに適用します。 ただし、データ自体の値は、 同じデータを指す別の参照経由で変化するかもしれません。 アプリケーション内で const を使用することで、 渡されたデータを変更せずに読み取りだけであるというインターフェイスを表現することができます。

invariant と const のどちらも、推移的です。つまり、 invariant な参照経由でアクセスできるデータは全てinvariantですし、 const の場合にも同様のことが成り立ちます。

invariant 記憶域クラス

一番単純な invariant は、記憶域クラスとしての使い方です。 これは記号定数(manifest constant)を宣言するのに使用することができます。

invariant int x = 3;	// x は 3 になる
x = 4;			// エラー。x は invariant
char[x] s;		// s は char 3つの配列

invariant変数の型は初期化子から推論されます:

invariant y = 4;	// int型のy
y = 5;			// えらー。y は invariant

初期化子を与えていない場合、 invariant を対応するコンストラクタで初期化することが可能です:

invariant int z;
void test()
{
    z = 3;		// エラー。z は invariant
}
static this()
{
    z = 3;    // ok
              // 静的初期化子のないinvariantの値は設定できる
}

非ローカルな invariant 宣言の初期化子はコンパイル時評価可能でなければなりません:

int foo(int f) { return f * 3; }
int i = 5;
invariant x = 3 * 4;      // ok, 12
invariant y = i + 1;      // エラー。コンパイル辞評価不可能
invariant z = foo(2) + 1; // ok。 foo(2) はコンパイル時に7に評価される

staticでないローカルなinvariant宣言の初期化子は、 実行時に評価されます:

int foo(int f)
{
  invariant x = f + 1;  // 実行時に評価
  x = 3;                // エラー。x は invariant
}

invariant は推移的なので、 invariantから参照されているデータもまたinvariantです:

invariant char[] s = "foo";
s[0] = 'a';  // エラー。s は invariant なデータを指している
s = "bar";   // エラー。s は invariant

invariant宣言はlvalueとして使うことができます。 つまり、アドレスを取ることが可能です。

const 記憶域クラス

const 宣言は、以下の違いを除いて、 invariant とほぼ同じです:

invariant 型

invariantと修飾された型の値は、決して変更されません。 予約語 invariant は type constructor として使うことができます:

invariant(char)[] s = "hello";

invarinatは、括弧の中に書かれた型に適用されます。 例えばこの例の場合、変数 s に別の値を代入することはできますが、 s[] の中身に別の値を入れることはできません:

s[0] = 'b';	// エラー。s[] は invariant
s = null;	// ok。sそのものはinvariantでない

invariant性は推移的です。つまり、invariant性は、 invariant型から参照されるすべてのものに伝搬します:

invariant(char*)** p = ...;
p = ...;	// ok, p はinvariantでない
*p = ...;	// ok, *p はinvariantでない
**p = ...;	// エラー。 **p は invariant
***p = ...;	// エラー。***p は invariant

記憶域クラスとして使われるinvariantは、 宣言の型全体をinvariantで修飾したのと同じ効果になります:

invariant int x = 3;   // x は invariant(int) 型
invariant(int) y = 3;  // y は invariant

invariant なデータを作る

一番単純な方法は、最初からinvariantとされているリテラルを使うことです。 例えば、文字列リテラルは invarinat です。

auto s = "hello";   // s は invariant(char)[5]
char[] p = "world"; // エラー。暗黙キャストで
		    // invariantを外すことはできない

二つめの方法は、データを明示的にinvariantにキャストすることです。 この方法をとるときは、 そのデータを他に書き換えるような参照がないことをプログラマが保証する必要があります。

char[] s = ...;
invariant(char)[] p = cast(invariant)s;     // 未定義動作
invariant(char)[] p = cast(invariant)s.dup; // ok。pがs.dupへの唯一の参照

.idup プロパティを使うと、 配列のinvariantなコピーを簡単に作ることができます:

auto p = s.idup;
p[0] = ...;	  // エラー。p[] は invariant

キャストでinvariantを取り除く

invariant をキャストで除去することは可能です:

invariant int* p = ...;
int* q = cast(int*)p;

しかし、だからといって、データを書き換えられるようになるわけではありません:

*q = 3; // コンパイルは通るが、未定義動作

invariant性を取り除くキャストは、正しく静的型がついていないくて、 しかもそれが修正できないという場面で必要となってしまうことがあります。 例えば、正しく型のついていないライブラリを使う場合などです。 キャストは使い方次第で毒にも薬にもなる道具です。 コンパイラが保証するinvariant性の正しさをキャストで取り除く以上、 データの invariant 性に関してプログラマが責任を持つことが前提となります。

invariant メンバ関数

invariant メンバ関数では、 this参照経由で参照される全てのデータがinvariantであることが保証されます。 以下のように宣言します:

struct S
{   int x;

    invariant void foo()
    {
	x = 4;	    // エラー。 x は invariant
	this.x = 4; // エラー。x は invariant
    }
}

const 型

const 型は invariant 型に似ていますが、const は データの読み込み専用の view を表します。 他に参照があってデータを書き換える可能性は残っています。

const メンバ関数

const メンバ関数は、 thisの指すオブジェクト自身のメンバを書き換えることが許されない メンバ関数です。

暗黙の変換

書き換え可能な型とinvariant型のデータは、constに暗黙変換できます。 書き換え可能な型をinvariantに変換したり、 その逆の変換が暗黙に行われることはありません。

Dのinvariant,const と C++のconstの比較

const, invariant の比較
機能 D C++98
予約語 const Yes Yes
予約語 invariant Yes No
constの記法 関数的:
// const int への const ポインタ へのポインタ
const(int*)* p;
後置:
// const int への const ポインタ へのポインタ
const int *const *p;
推移的 const Yes:
// const intへのconstポインタへのconstポインタ
const int** p;
**p = 3; // エラー
No:
// intへのポインタへのconstポインタ
int** const p;
**p = 3;    // ok
const除去キャスト Yes:
// const intへのポインタ
const(int)* p;
int* q = cast(int*)p; // ok
Yes:
// const intへのポインタ
const int* p;
int* q = const_cast<int*>p; //ok
const除去後の書き換え No:
// const intへのポインタ
const(int)* p;
int* q = cast(int*)p;
*q = 3;   // 未定義動作
Yes:
// const intへのポインタ
const int* p;
int* q = const_cast<int*>p;
*q = 3;   // ok
トップレベルのconstでのoverload Yes:
void foo(int x);
void foo(const int x);  // ok
No:
void foo(int x);
void foo(const int x);  // エラー
constとmutableの別名参照 Yes:
void foo(const int* x, int* y)
{
   bar(*x); // bar(3)
   *y = 4;
   bar(*x); // bar(4)
}
...
int i = 3;
foo(&i, &i);
Yes:
void foo(const int* x, int* y)
{
   bar(*x); // bar(3)
   *y = 4;
   bar(*x); // bar(4)
}
...
int i = 3;
foo(&i, &i);
invariantとmutableの別名参照 Yes:
void foo(invariant int* x, int* y)
{
   bar(*x); // bar(3)
   *y = 4;  // 未定義動作
   bar(*x); // bar(??)
}
...
int i = 3;
foo(cast(invariant)&i, &i);
invariant はナシ
文字列リテラルの型 invariant(char)[] const char*
文字列リテラルから非const型への暗黙変換 なし あり、ただし非推奨