placement new
Adaがいまいち書きにくい理由のひとつに、placement newが無いことが挙げられます。いやまあplacement newが無くて書きにくいのなんかコンテナぐらいだろってのは間違いないですけどね。
一応説明しておきますと、placement newといいますのはC++での名称で、Pascalでは(System.)Initialize/Finalizeが対応します。そう、AdaはPascalにすら劣っていたのです。えーと、具体的には、メモリ確保/開放を伴わない、任意のアドレスのメモリを、任意の型として再初期化/後始末する機能です。これが無いからAda.Containers.Vectorsはダメダメなんです。あってもダメダメでしょうけど……。とりあえずこれが無いと、Vectorsに限らずギャップバッファやPiece Tableなど未使用エリアを確保しておくタイプのgenericコンテナを安心して書き始めることができないじゃないですか。
こういった機能はコンパイラに用意されない限り、原則どうにもなりません。System.Finalization.Initializeを呼んでみたところで、明示的にInitializeをoverridingした処理だけ、C++のコンストラクタで言えば{ }の中が実行されるだけです。宣言中でデフォルト値を与えられていたりして静的に初期化される部分、C++のコンストラクタで言えば: this(...), x(...), y(...)の部分は実行できません。そもそも外から実行できる形になっているすなわち動的に解決しているようではpragma Preelaborable_Initializationの存在が泣きます。つまりpragma Preelaborable_Initializationを付けたにも関わらず何かにつけ動的に初期化をしようとするGNATフロントエンドは殴っていいと思われます。
それはともかく、今まで歯がゆい思いをしていたのですが、このたび実現手段を思いつきましたので、書いておきます。
まず、ダミーのストレージプールを用意します。
with System.Storage_Elements; with System.Storage_Pools; package Overwriting_Pools is pragma Preelaborate; type Overwriting_Pool is new System.Storage_Pools.Root_Storage_Pool with record Address : System.Address; end record; overriding procedure Allocate(Pool : in out Overwriting_Pool; Storage_Address : out System.Address; Size_In_Storage_Elements : in System.Storage_Elements.Storage_Count; Alignment : in System.Storage_Elements.Storage_Count); overriding procedure Deallocate(Pool : in out Overwriting_Pool; Storage_Address : in System.Address; Size_In_Storage_Elements : in System.Storage_Elements.Storage_Count; Alignment : in System.Storage_Elements.Storage_Count) is null; overriding function Storage_Size(Pool : Overwriting_Pool) return System.Storage_Elements.Storage_Count; Pool : Overwriting_Pool; end Overwriting_Pools;
package body Overwriting_Pools is overriding procedure Allocate(Pool : in out Overwriting_Pool; Storage_Address : out System.Address; Size_In_Storage_Elements : in System.Storage_Elements.Storage_Count; Alignment : in System.Storage_Elements.Storage_Count) is begin Storage_Address := Pool.Address; end Allocate; overriding function Storage_Size(Pool : Overwriting_Pool) return System.Storage_Elements.Storage_Count is begin return 0; end Storage_Size; end Overwriting_Pools;
……要するにこれが全てなんですけどね。
Overwriting_Pools.Pool.Addressに目的のアドレス突っ込んでnewすればOKというしまらない実現方法です。終了処理も、このプール上でAda.Unchecked_Deallocationすれば、終了処理だけしてくれてメモリ開放は行いませんので目的通りです。
Overwriting_Pools.Pool.Addressはグローバル変数には間違いありませんので、実際にはクリティカルセクションで挟んだほうがいいでしょうね。
一応、使いやすくラップしたものを置いておきます。
generic type T is limited private; package Lifetime is pragma Preelaborate; procedure Initialize(Address : not null access T); pragma Inline(Initialize); procedure Finalize(Address : not null access T); pragma Inline(Finalize); end Lifetime;
with Ada.Unchecked_Deallocation; with Overwriting_Pools; with System.Aux_DEC; package body Lifetime is pragma Suppress(All_Checks); use System.Aux_DEC; type A is access all T; for A'Storage_Pool use Overwriting_Pools.Pool; procedure Initialize(Address : not null access T) is Z : A; begin case T'Type_Class is when Type_Class_Enumeration | Type_Class_Integer | Type_Class_Fixed_Point | Type_Class_Floating_Point | Type_Class_Address => null; when others => Overwriting_Pools.Pool.Address := Address.all'Address; Z := new T; end case; end Initialize; procedure Finalize(Address : not null access T) is procedure Free is new Ada.Unchecked_Deallocation(T, A); Z : A := Address.all'Unchecked_Access; begin case T'Type_Class is when Type_Class_Enumeration | Type_Class_Integer | Type_Class_Fixed_Point | Type_Class_Floating_Point | Type_Class_Address => null; when others => Free(Z); end case; end Finalize; end Lifetime;
何もしないとはいえ動的束縛になるストレージプールはインライン化できませんので……嘘です。ストレージプールはaccess型に対して静的に解決されます。なのにpragma Inline書いてもインライン化できません。仕方が無いので、元々初期化不要の型については、処理を行わないようにしています。流石に静的な条件式が与えられたcase文の削除ぐらいはしてくれますので、賢いgccは。