構造体
Midoliy|F#プログラミング

構造体の概要
サンプルでみる構造体

構造体の概要


 構造体(= struct)は、ごく軽量なデータメンバや非常に単純な振舞いのみを持つコンパクトなオブジェクト型です。また、クラスよりも効率的に動作する場面が多いです。
 クラスと違い構造体は値型であるため、作成した値は スタックメモリ に直接格納されます。フィールドや配列要素として使用される場合には、親の型にインラインで格納されます。また、構造体は値渡しのセマンティクスを持つため、頻繁にアクセス・コピーされるような小さなデータ集合体をまとめるのに向いています。

 以下は構造体の構文になります。
// ------------------
// [ 構文 ]
// ------------------
[<attributes>]
type [ accessibility-modifier ] struct-name = struct
    value-list
    [ additional-constructor ]
    member-list
end

// or

[<attributes>]
[<Struct>]
type [ accessibility-modifier ] struct-name =
    value-list
    [ additional-constructor ]
    member-list
                

 構文を見てもらうとわかるとおり、構造体には let束縛 も do束縛 も含めることができません。また、独自の型のフィールドを再帰的に含めることができないことにも注意が必要です。


 各要素については次節で紹介しますので、今節は PointA構造体 の struct - end の部分に注目していきます。これは 冗語構文 と呼ばれる記述方法です。また、PointB構造体 のような方法(= [<Struct>]属性を付与し、オフサイドルールを利用する方法)を 軽量構文 と呼びます。また、[<Struct>] を付けなくとも構造体の軽量構文を利用することは可能です。属性の付与はあくまでもオプションなので覚えておきましょう。
 また、冗語構文と軽量構文のメリット・デメリットについては こちら をご参照ください。
 

サンプルでみる構造体


 前節で構造体の簡単な構文を紹介しました。本節では簡易的なサンプルを利用して、実際の値と構文の説明がどう対応しているかを見ていきたいと思います。今回のサンプルは前節と同様の Point型 を利用していきたいと思います。
 繰り返しとなりますが、下記は Point型 の定義となります。


 下記の表は内包している要素と構文の要素名とを対応付けたものとなります。ただし重複する内容のものに関しては省略しています。
 また、改めて構造体の宣言構文も記載しています。

// ------------------
// [ 構文 ]
// ------------------
[<attributes>]
type [ accessibility-modifier ] struct-name = struct
    value-list
    [ additional-constructor ]
    member-list
end
                

要素名 対応するPoint型の要素
struct-name Point
value-list x:float
y:float
additional-constructor new (x:float, y:float)
member-list member this.getDistanceFrom (p:Point)

 構造体の構成はクラスに比べると単純ですが、レコード型などと比べると複雑です。
 最初に [ struct-name ] を指定しています。デフォルトコンストラクタはクラスとは違い、unit型の引数と決まっているため、明示的に指定することはできません。
 また [ value-list ] には、構造体に属する値を宣言していきます。ここで注目すべきは valキーワード です。今までは let束縛 などを利用してきましたが、構造体ではデフォルトコンストラクタがunit型となっている関係で利用できません。let束縛は値の初期化を強制しますが、valキーワードはそれとは逆に値の初期化を許容しません。そのため、valキーワードで宣言された値は 0 または null で自動的に初期化されます。
 構造体ではデフォルトコンストラクタが予約されているため、基本的に追加のコンストラクタ(= [ additional-constructor ]) を記述することになると思います。これはサンプルの通り、newキーワード に対して引数に取りたい値をtuple型で宣言します。そして、その値を利用して、パブリックフィールドを初期化するようにします。
 また、構造体はメンバを持つことができます。ただし、クラスとは異なり単純かつ軽量な処理となるように設計しなければなりません。構造体とクラスはできることが近いせいでどちらを使えばよいかわからなくなるかもしれませんが、Microsoftは明確にその使い分けのガイドラインを提示しています。要約すると、次の4つすべてに当てはまらない場合はクラスにするべきとのことです。

struct を使うべき状況の条件
プリミティブ型と同じように、単一の値を論理的に表す
インスタンスのサイズが16byte未満
変更されない
頻繁にボックス化しない

 クラスや構造体、レコードなど似たような機能がありますが、それぞれに適した場所は違います。最初は使い分けの基準がわからないかもしれません。しかし、コーディングを積み重ね、失敗を積み重ねることによって徐々に使い分けられるようになると思います。