Const と Immutable
データ構造やインターフェイスを調べるときには、 どのデータが不変でどのデータが可変で、変更を行う可能性があるのは誰か、 といった情報が簡単にわかると非常に便利です。 そしてこれは、言語の型システムの助けによって実現することができます。 データは const あるいは immutable とマーク付けすることができ、 デフォルトでは変更可能 (mutable) となっています。
immutable は、 一度構築されたら決して変更されることのないデータに適用します。 immutable なデータ値は、 プログラムの実行中一貫して同じ値であり続けます。 immutable なデータは ROM (Read Only Memory) や、ハードウェアによって読み取り専用とされたメモリページに配置することができます。 immutable なデータは決して変更されないので、 さまざまなプログラム最適化の機会をもたらします。また、 アプリケーションをより関数型のプログラミングスタイルで書くことを可能にします。
const は、そのconst参照を通しては変更を加えることのないデータに適用します。 ただし、データ自体の値は、 同じデータを指す別の参照経由で変化するかもしれません。 アプリケーション内で const を使用することで、 渡されたデータを変更せずに読み取りだけであるというインターフェイスを表現することができます。
immutable と const のどちらも、推移的 です。つまり、 immutable な参照経由でアクセスできるデータは全てimmutableですし、 const の場合にも同様のことが成り立ちます。
immutable 記憶域クラス
一番単純な immutable は、記憶域クラスとしての使い方です。 これは記号定数(manifest constant)を宣言するのに使用することができます。
immutable int x = 3; // x は 3 になる x = 4; // エラー。x は immutable char[x] s; // s は char 3つの配列
immutable変数の型は初期化子から推論されます:
immutable y = 4; // int型のy y = 5; // エラー。y は immutable
初期化子を与えていない場合、 immutable を対応するコンストラクタで初期化することが可能です:
immutable int z; void test() { z = 3; // エラー。z は immutable } static this() { z = 3; // ok // 静的初期化子のないimmutableの値は設定できる }
非ローカルな immutable 宣言の初期化子はコンパイル時評価可能でなければなりません:
int foo(int f) { return f * 3; } int i = 5; immutable x = 3 * 4; // ok, 12 immutable y = i + 1; // エラー。コンパイル時評価不可能 immutable z = foo(2) + 1; // ok。 foo(2) はコンパイル時に7に評価される
staticでないローカルなimmutable宣言の初期化子は、 実行時に評価されます:
int foo(int f) { immutable x = f + 1; // 実行時に評価 x = 3; // エラー。x は immutable }
immutable は推移的なので、 immutableから参照されているデータもまたimmutableです:
immutable char[] s = "foo"; s[0] = 'a'; // エラー。s は immutable なデータを指している s = "bar"; // エラー。s は immutable
immutable宣言はlvalueとして使うことができます。 つまり、アドレスを取ることが可能です。
const 記憶域クラス
const 宣言は、以下の違いを除いて、 immutable とほぼ同じです:
- const宣言された変数を通してデータを書き換えることはできないが、 同じデータを参照する別の箇所がデータを書き換えることは あるかもしれない
- const宣言された変数の型は、const
immutable 型
immutableと修飾された型の値は、決して変更されません。 予約語 immutable は type constructor として使うことができます:
immutable(char)[] s = "hello";
immutable は、括弧の中に書かれた型に適用されます。 例えばこの例の場合、変数 s に別の値を代入することはできますが、 s[] の中身に別の値を入れることはできません:
s[0] = 'b'; // エラー。s[] は immutable s = null; // ok。sそのものはimmutableでない
immutable性は推移的です。つまり、immutable性は、 immutable型から参照されるすべてのものに伝搬します:
immutable(char*)** p = ...; p = ...; // ok, p はimmutableでない *p = ...; // ok, *p はimmutableでない **p = ...; // エラー。 **p は immutable ***p = ...; // エラー。***p は immutable
記憶域クラスとして使われる immutable は、宣言の型全体をimmutableで修飾したのと同じ効果になります:
immutable int x = 3; // x は immutable(int) 型 immutable(int) y = 3; // y は immutable
immutable なデータを作る
一番単純な方法は、最初からimmutableとされているリテラルを使うことです。 例えば、文字列リテラルは immutable です。
auto s = "hello"; // s は immutable(char)[5] char[] p = "world"; // エラー。暗黙キャストで // immutableを外すことはできない
二つめの方法は、データを明示的にimmutableにキャストすることです。 この方法をとるときは、 そのデータを他に書き換えるような参照がないことをプログラマが保証する必要があります。
char[] s = ...; immutable(char)[] p = cast(immutable)s; // 未定義動作 immutable(char)[] p = cast(immutable)s.dup; // ok。pがs.dupへの唯一の参照
.idup ロパティを使うと、 配列のimmutableなコピーを簡単に作ることができます:
auto p = s.idup; p[0] = ...; // エラー。p[] は immutable
キャストでimmutableを取り除く
immutable をキャストで除去することは可能です:
immutable int* p = ...; int* q = cast(int*)p;
しかし、だからといって、データを書き換えられるようになるわけではありません:
*q = 3; // コンパイルは通るが、未定義動作
immutable性を取り除くキャストは、正しく静的型がついていないくて、 しかもそれが修正できないという場面で必要となってしまうことがあります。 例えば、正しく型のついていないライブラリを使う場合などです。 キャストは使い方次第で毒にも薬にもなる道具です。 コンパイラが保証するimmutable性の正しさをキャストで取り除く以上、 データの immutable 性に関してプログラマが責任を持つことが前提となります。
immutable メンバ関数
immutable メンバ関数では、 this 参照経由で参照される全てのデータがimmutableであることが保証されます。 以下のように宣言します:
struct S { int x; void foo() immutable { x = 4; // エラー。 x は immutable this.x = 4; // エラー。 x は immutable } }
関数の左側に immutable を書くのは、返値型への修飾にはなりません:
struct S { immutable int[] bar() // bar は immutable ですが、返値型はそうではありません! { } }
返値型を immutable にするには、括弧で囲う必要があります:
struct S { immutable(int[]) bar() // 今度は bar は mutable で、返値型が immutable. { } }
メソッドと返値の両方を immutable にするには、こうします:
struct S { immutable(int[]) bar() immutable { } }
const 型
const 型は immutable 型に似ていますが、const は データの読み込み専用の view を表します。 他に参照があってデータを書き換える可能性は残っています。
const メンバ関数
const メンバ関数は、 thisの指すオブジェクト自身のメンバを書き換えることが許されない メンバ関数です。
暗黙の変換
書き換え可能な型とimmutable型のデータは、constに暗黙変換できます。 書き換え可能な型をimmutableに変換したり、 その逆の変換が暗黙に行われることはありません。
Dのimmutable
const と C++のconstの比較,機能 | D | C++98 |
---|---|---|
予約語 const | Yes | Yes |
予約語 immutable | 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);
|
immutableとmutableの別名参照 | No:
void foo(immutable int* x, int* y) { bar(*x); // bar(3) *y = 4; // 未定義動作 bar(*x); // bar(??) } ... int i = 3; foo(cast(immutable)&i, &i); |
immutable はナシ |
文字列リテラルの型 | immutable(char)[] | const char* |
文字列リテラルから非const型への暗黙変換 | なし | あり、ただし非推奨 |