[1] データの不変性
あるデータが書き換えられるかどうか、そして、書き換えてもいいのかどうかということは、プログラムの信頼性を高めるキーポイントになることがあります。要するに、書き換えてはいけないデータには、それを明示する手段があれば、そのデータを扱うコードの信頼性が高まりますし、場合によっては最適化に役立つかもしれません。
D言語には、データの不変性を表す修飾に、constとimmutableというものがあります。これらの使い方は、次の3種類です。
1.記憶クラスとしての使用法
const,immutableは、変数の記憶クラスとして用いることが出来ます。これは、変数宣言の型名の前に付けるだけです。この場合、型推論を行わせることが可能です。
const int a = 100;
immutable double pi = 3.14;
const data = [1,2,3,4,5]; //型推論 dataの型は const int[5]
immutable e = 2.718f; //これも型推論 eの型はfloat
型コンストラクタとは、既存の型にconstやimmutableを付加した型を作るというものです。これらは、次のように宣言されます。
const(int) immutable(int)[] const(float)*
要するに、const(型名) immutable(型名) のように宣言することで、これらの性質を持つ新たな型が生成されるわけです。
*const,immutableの推移性
D言語のconst,immutableを理解する肝になる部分ですが、これらには、「推移性」という性質があるのです。推移性とは何かというと、constから参照されている値はconstになり、immutableから参照されている値はimmutableになるという性質のことです。これの分かりやすい例が、ポインタになります。例えば、次のような宣言があったとします。
const int ** ppi = ………;
このとき、ppi自体も書き換え不可ですし、*ppiも書き換え不可能なのです。さらに、**ppiも書き換えることが出来ないです。要するに、次のようなコードはすべてエラーになるわけです。
また、
immutable(int**)* pppi;
という宣言があったとき、pppi自体は書き換えることが出来るのですが、pppiが参照しているアドレス以降はすべて書き換えることが出来ないのです。つまり、こういうこと。
pppi = &pptr; //OK 正しい操作。
*pppi = &ptr; //エラー
**pppi = &i; //エラー
***pppi = 300; //エラー
このように、constやimmutableへのmutableポインタは作ることが出来るのですが、逆、つまり、ポインタ値は書き換えられないが、参照先は書き換えられる。というような変数を作ることは、D言語では許されていないのです。
このような性質が現れるもう一つの例が、動的配列です。例えば、要素がimmutable(int)の、mutableの配列は、次のように宣言します。
immutable(int)[] iarray = [1,2,3,4,5]; //配列リテラルはimmutable(int)[]を初期化できる。
iarray = other.idup; //ある配列の、要素型がimmutableなコピーを得るには、idupプロパティを用いる。
► スポイラーを表示
constやimmutableを使えば、書き換えできない定数という事を表現できるのですが、では、constとimmutableは何が違うのかということが、問題となってきます。これらは、「書き換えられない」というのは共通しているのですが、意味がちょっと異なるのです。constは、「読み取り専用」という意味で、immutableは、「絶対不変」を表すのです。これらは、基本データ型などの、値型に使われた場合は、ほとんど違いが現れませんが、参照型に使われた場合、意味が変わってくるのです。
例えば、immutable(int)* と、const(int)* を例に考えてみようと思います。
int *mpi;
const(int)* cpi;
immutable(int)* ipi;
という宣言があったとします。当然、次のような操作は禁じ手です。参照先を書き換えることは出来ません。
*cpi = 100;
*ipi = 100;
ですが、cpiの参照先は、他の参照によって書き換えられる可能性があるのです。例えば、int型のmutableな変数iがあるとすると、次のような代入が出来ます。
cpi = &i;
( 当然のことですが、mpi = &i; というのも正しいです。)
このとき、cpiを通じて参照先の変数i を書き換えることは出来ませんが、このとき、cpiの参照先自体は、書き換わることがありますよね? これが、constの意味なのです。constが読み取り専用であるというのは、constが付けられた変数を通して書き換えることを禁止するだけという意味です。
一方で、immutableの方はというと、実は、参照先がimmutableの場合、参照先の値が変わることは絶対にないのです。つまり、ipiが指すアドレスが変化しない限り、*ipiの値は一定になるのです。なので、次の代入はコンパイルエラーになります。なぜなら、参照先がimmutableではないからです。
ipi = &i;
これが、immutableの意味です。それと、immutable型とmutable型はいずれも、暗黙的にconstに変換することが出来ます。つまり、次のコードは安全かつ合法です。
cpi = ipi; //immutable→constの変換
cpi = mpi; //mutable→constの変換
これは、動的配列型にも当てはまります。つまり、要素型がmutableな動的配列は、immutableな配列に暗黙変換することは出来ませんが、constな配列には暗黙変換することが出来ます。つまり、ポインタと配列について、次の暗黙変換が実行可能です。
前の記事「配列(2)」 ←→ 次の記事「オブジェクト指向とクラス(1)」