Table of Contents
Delphiのマネージド レコードとは?
Delphiのレコード型では、任意のデータ型フィールドを持つことができます。レコードが数値型や列挙値などの(管理されない)単純なデータ型フィールドを持つ場合、コンパイラが行うことはあまりありません。レコードの作成と破棄は、メモリ割り当てと割り当てたメモリ領域の解放のみです(Delphiでは、デフォルトでレコードをゼロ初期化しない点に注意してください)。
レコードが、コンパイラによって管理されるデータ型のフィールド(string型やinterfaceなど)を持つ場合、コンパイラは初期化やファイナライズを管理するためのコードを挿入しなければなりません。例えば、string型は参照カウントされるため、レコードがスコープから外れたときに、レコード内のstring型フィールドの参照カウントを減らす必要があり、その結果、このフィールドのメモリの割り当てが解除される可能性もあります。そのため、コードの中でそのようなマネージド レコードを使用している場合には、コンパイラは自動的にそのコードブロックをtry-finallyで囲い、例外が発生した場合でもデータが確実にクリアされるようにします。このような動作は、長い期間サポートされてきました。つまり、マネージド レコードはDelphi言語の一部になっていました。
InitializeおよびFinalize演算子を持つレコード
10.4では、新たにDelphiのレコード型で、カスタム初期化ならびにファイナライズをサポートするようになりました。これにより、マネージド レコードに対して、コンパイラのデフォルト操作を上書きすることができます。フィールドのデータ型にかかわらず、カスタム初期化およびファイナライズコードを伴ってレコードを宣言することができ、初期化とファイナライズのためのカスタムコードを記述できます。これは、特定の新しい演算子をレコード型に追加することによって可能になります(不要な場合には、いずれかの演算子の省略も可能です)。
以下は、シンプルなコード例です。
もちろん、対応する2つのクラス メソッド コードを記述しなければなりません。例えば、実行ログの出力やレコード値の初期化などを記述します。この例では、メモリロケーションへの参照もログに出力し、どのレコードに対して操作を行っているのかを確認できるようにしています。
このコンストラクタ メカニズムと従来のレコードとの大きな違いは、自動呼び出しに関する差です。以下のコードのような記述を行うと、イニシャライザとファイナライザの両方を呼び出すことができ、最終的に、マネージド レコードのインスタンス用にコンパイラが生成したtry-finallyブロック内で実行されます。
このコードを実行すると、次のようなログが出力されます。
別のシナリオは、インライン変数として使用するケースです。
この場合でも同じようなログが出力されます。
演算子の割り当て
代入演算子 := には、レコードのすべてのフィールドデータをそのままコピーする動作が割り当てられています。この動作は、デフォルトとして妥当ですが、カスタムデータフィールドとカスタム初期化を持つ場合には、この動作を変更する必要が出てくるかもしれません。これが、カスタム マネージド レコード向けに、代入演算子を定義できる理由になります。新しい演算子は := 構文で使用できますが、定義には、Assign を使います。
この演算子の定義は、非常に正確なルールに則って行う必要があります。最初のパラメータは参照パラメータ、2番目のパラメータは参照渡しのconst型になります。この規則に従わない場合、コンパイラは以下のようなエラーメッセージを出力します。
以下は、Assign演算子を用いた例です。
この結果、以下のようなログが出力されます(レコードにシーケンス番号も追加してみました)。
破棄の順番が、生成とは逆の順序になっていることに注意してください。
マネージド レコードをパラメータとして渡す
マネージド レコードは、パラメータとして渡されたり、関数の戻り値として返される場合にも、通常のレコードとは異なる動作が可能です。以下は、さまざまなシナリオの例です。
ここでは、ひとつひとつのログを確認することはせず、以下に要約することにします。
- ParByValue は、新しいレコードを作成し、割り当て演算子がある場合には、これを呼び出しデータをコピーします。そして、プロシージャを終了するときにこの一時コピーを破棄します。
- PParByConstValue は、コピーを行わず、何も呼び出されません。
- ParByRef も、コピーを行わず、何も呼び出されません。
- ParByConstRef も、コピーを行わず、何も呼び出されません。
- ParReturned は、(Initializeを用いて)新しいレコードを作成し、Assign演算子を呼び出すことでそれを返します。my1:= ParReturned; のような呼び出しでは、割り当てが行われた一時レコードを削除します。
例外とマネージド レコード
明示的なtry-finallyブロックが存在しない場合でも、例外が発生したときには、一般的にレコードはクリアされます。これはオブジェクトとは異なる動作です。これは根本的な違いであり、マネージド レコードの実際の有用性のポイントです。
このプロシージャ内では、コンストラクタ呼び出しが2回、デストラクタ呼び出しが2回発生します。繰り返しますが、これがマネージド レコードの根本的な違いであり、ポイントとなる機能です。マネージド レコードをベースとした、簡単なスマートポインタについては、次のセクションを参照ください。
マネージド レコードの配列
マネージド レコードの静的配列を定義すると、宣言時に初期化演算子を用いた初期化を行うことができます。
この配列は、スコープから外れるとすべて破棄されます。マネージド レコードの動的配列を宣言すると、初期化コードは、(SetLengthを使って)配列サイズを設定した際に呼び出されます。
まとめ
ここでは、エンバカデロが次の10.4リリースで、Delphi言語に追加するすばらしい新機能について簡単に紹介しました。マネージド レコードは、例えばジェネリック レコードをはじめ、ここでは紹介しきれないような、さまざまなシナリオで活用できます。ここで紹介した新機能は、言語に関するものでしたが、このほかにも、すべてのプラットフォームで共通したメモリ管理などもあります。ぜひ、ご期待ください。
アップデートサブスクリプションに加入している方は、ベータビルドへのアクセスが可能です。現在でも10.4のベータプログラムに参加可能ですので、お問い合わせください。
この記事は、RAD Studioの将来のリリースに関するプレビューです。製品品質やスケジュール等の理由により、紹介した機能の提供が変更になる可能性があります。製品が正式にリリースされるまで、最終的な機能、スペック等について保証されません。