構造体は複数の型を一つにまとめた新たな型を作るものでした。 共用体も複数の型をまとめた新しい型を作ります。
共用体と構造体の違いは、構造体は直積的であるのに対して、共用体は直和的です。 つまり、もっと簡単な言葉を使えば、構造体は「複数の型の値のペア」であるのに対して、共用体は「一つの値が複数の型になり得る」のです。
共用体と構造体のメモリ上でのメンバの配置は次のようになります。 構造体はメンバそれぞれに領域が割り当てられるのに対して、共用体ではすべてのメンバで領域を共有します。
たとえば、int
型とstring
型から成る共用体型の値には、int
型の値も格納可能ですし、string
型の値も格納可能です。
メンバは互いにメモリを共有しているため、あるメンバへ書き込みを行えば他のメンバの値を破壊します。
そのため、あるメンバへ書き込みをした後に違うメンバを読むと、意図しない値になる可能性があります。
import std.stdio;
union IntOrString
{
int n;
string str;
}
void main()
{
IntOrString ios = {str : "123"};
writeln(ios.str); // 123
writeln(ios.n); // ???
// ↑でどんな値が表示されるか環境依存
}
共用体は一つの変数で複数の型の値を代入できるので、使い方を誤らないのであれば非常に有用なプログラムを書くことができます。 その典型的な例として、動的型付け言語とのデータのやりとりが挙げられます。 D言語は静的型付け言語ですから、すべてのデータは何らかの型に属していることがコンパイル時に決定されています。 対して、RubyやPython、JavaScriptなどの動的型付けな言語では、そのデータがどの型に属しているかは実行時にしかわかりません。 そのような動的型付けされたデータを静的型付け言語に渡す場合には、共用体のように複数の型の値をまとめることができるデータ型が必要となります。
しかし、共用体をそのまま扱うことは非常に危険を伴いますから、Dの標準ライブラリには安全なデータ型が定義されています。
それが、Variant
とかAlgebraic
というデータ型で、std.variant
で定義されています。
これら2つの型をうまく使うには、この章よりももっと後の章の知識を必要としますから、ここでは説明しません。
もし共用体を使いたくなった場合にはVariant
やAlgebraic
という安全な型が存在することを思い出してみてください。