2015-11-06 7 views
17

Cでは、ライブラリが、malloc()と同様に動作する関数へのグローバル関数ポインタとfree()と同様に動作する関数を使用して、メモリ割り当てをカスタマイズできるようにするのは簡単です。例えば、SQLiteはこのアプローチを使用します。C++ライブラリはカスタムアロケータをどのように許可するべきですか?

割り当てと初期化が通常融合されているため、C++は物事を少し複雑にします。本質的には、ライブラリーのみにオーバーライドされたoperator newoperator deleteの動作を取得したいのですが、実際にはその方法はありません(私はかなり確かですが、100%ではありません)。

これはC++でどのように行うべきですか?

Hereは、Lib::make<T>の機能を持つnew式のセマンティクスの一部を複製するもので最初のスタブです。

これはとても役に立ちますが、楽しみのためだけに、herenew[]式のセマンティクスを複製しようとするより複雑なバージョンです。

これは目標指向の質問ですので、必ずしもコードレビューを探しているわけではありません。これを行うためのよりよい方法がある場合は、そう言い、リンクを無視してください。

(「アロケータ」と私は唯一のメモリを割り当てる何かを意味、私はSTLアロケータの概念を参照するかさえ、必ずしもコンテナのメモリを割り当てていないよ。。)


なぜこれが望ましいかもしれない。

Here Mozillaデベロッパーのブログ記事で、ライブラリがこれを行うべきだと主張しています。彼は、ライブラリユーザがライブラリの割り当てをカスタマイズできるようにするCライブラリの例をいくつか挙げています。私は、SQLiteのサンプルコードの1つをチェックアウトし、この機能がフォルトインジェクションによるテストのために内部でも使用されていることを確認しました。私はSQLiteのように防弾でなければならないものは書いていませんが、それはまだ賢明な考えのようです。それ以外のものがあれば、クライアントコードは「どのライブラリが私の記憶をいつまでにしていますか?」と把握することができます。

+2

厳密には*より優れていますが、標準ライブラリでは、コンテナー割り当てを行うために 'allocator'すなわち' allocator_traits'を使用しています。 – BeyelerStudios

+2

これを行うことをお勧めします。標準的な手法は、割り当てを実行する型にテンプレート引数を介してカスタムアロケータを受け入れることです。残念なことに、これはすべての型をテンプレートにする必要があり、望ましくない可能性があります。アロケータへの多態的なアプローチについては、http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3525.pdfをご覧ください。 – pmr

+0

あなたがアロケータstdの方法を検討する別のものは、 'string_view'' array_view'型を追加して、複数の無関係な型(raw配列、std :: array、std :: vector、customベクター等)。 – Alex

答えて

5

単純な答え:C++を使用しないでください。申し訳ありません、ジョーク。

しかし、このような絶対的な制御をC++やライブラリ/モジュールの境界を越えて完全に一般化された方法で行うには、恐ろしい悲しみの中にいます。私はそれを行う方法以上にそれをしない理由を探すためにほとんどの人にお勧めします。

オペレータの新規/新規[] /削除/削除[]をグローバルレベルでリンカベースのソリューションに単純にオーバーロードしようとすることから、この同じ基本的アイデア(何十年も)私はプラグインごとに割り当てられるメモリの量を見ることができるシステムを持っています。しかし、私は、あなたが望む一般的なやり方(そして、もともと私も同様)を通して、この点には達しませんでした。

割当ておよび初期化は通常融合 あるので物事ビットを複雑++

C。

私はこの声明にわずかなねじれを提供します:C++は、物事を複雑に初期化し、配分は通常を融合しているため。私がやったのはここで命令を入れ替えるだけでしたが、最も複雑な部分は、割り当てが初期化したいのではなく、初期化がしばしば割り当てたいからです。

は、この基本的な例ください:この場合

struct Foo 
{ 
    std::vector<Bar> stuff; 
}; 

を、我々は簡単にカスタムメモリアロケータ経由のFooを割り当てることができます。

void* mem = custom_malloc(sizeof(Foo)); 
Foo* foo = new(foo_mem) Foo; 
... 
foo->~Foo(); 
custom_free(foo); 

...そして、もちろん、私たちはこのすべてをラップすることができ、我々 RAIIに準拠したり、例外安全性を達成したりすることができます。

問題はカスケードします。そのstuffメンバーはstd::vectorを使用して、std::allocatorを使いたいと思っています。独自のアロケータを使用してテンプレートインスタンス化std::vectorを使用することができます。アロケータに渡されるランタイム情報が必要な場合は、Fooのコンストラクタをオーバーライドして、その情報をアロケータとともにベクトルコンストラクタに渡すことができます。

しかし、何について約Bar?そのコンストラクタは、さまざまな異種のオブジェクトにメモリを割り当てたいことがあるため、問題はカスケードし、カスケードし、カスケードします。

この問題の難しさと、私が試した代替的な一般化された解決策と移植時の悲しみを考えると、私は完全に非一般化されたやや実用的なアプローチに着手しました。

私が解決した解決策は、CおよびC++の標準ライブラリ全体を効果的に改革することです。うんざりする、私は知っているが、私は私のケースでそれを行うにはもう少し言い訳があった。私が取り組んでいる製品は、コンパイラ、Cランタイム、C++標準ライブラリの実装、そして彼らが望む設定を使ってプラグインを書くことができるように設計されたエンジンとソフトウェア開発キットです。ベクターやセットやマップのようなものをABI互換の方法でこれらの中心的なAPIに通すことを可能にするためには、多くのC標準関数に加えて独自の標準準拠のコンテナを使用する必要がありました。

この開発キットの全体の実装は、これらの割付機能を中心に展開:

EP_API void* ep_malloc(int lib_id, int size); 
EP_API void ep_free(int lib_id, void* mem); 

...とSDKの全体がメモリ・プールと「サブアロケータ」を含む、これらの二つの中心に展開。

私たちのコントロール外にある第三者のライブラリの場合、私たちはただのSOLです。これらの図書館の中には、記憶管理をしたい野心的なものもありますし、それを無効にしようとするとあらゆる種類の衝突につながり、すべての種類のワームを開くことができます。 OGLのようにシステムメモリを大量に割り当てたい場合、非常に低レベルのドライバがあります。何もできません。

しかし、私はこの解決策が基本的な質問に答えるのに十分にうまくいくことを発見しました。「誰がこのすべての記憶を突き詰めているのですか?非常に素早く:クロックサイクルに関連する同様の質問(これは単にプロファイラを起動することができます)よりも、回答するのがずっと難しい質問です。このSDKを使用して、私たちが管理しているコードにのみ適用されますが、モジュールごとにこのシステムを使用して非常に徹底したメモリ故障を得ることができます。また、実際にシステム内で使用可能なすべての連続したページを使い果たすことなく、メモリ不足エラーが実際に正しく処理されているかどうかを確認するために、メモリ使用に関する表層キャップを設定することもできます。

私の場合、この問題はポリシーによって解決されました:コードベース全体(および私たちのシステム用のプラグインを書く第三者)によって使用される統一コーディング標準とそれに準拠する中央ライブラリを構築する。これはおそらくあなたが探している答えではありませんが、これは私たちが見つけた最も実用的な解決策に終わってしまいました。

関連する問題