C++ の「placement new」を使用したことがある人はいますか? もしそうなら、何のために?メモリマップされたハードウェアでのみ役立つように思えます。
25 に答える
Placement new を使用すると、既に割り当てられているメモリ内にオブジェクトを構築できます。
オブジェクトの複数のインスタンスを構築する必要がある場合、最適化のためにこれを行うことができ、新しいインスタンスが必要になるたびにメモリを再割り当てしない方が高速です。代わりに、一度にすべてを使用したくない場合でも、複数のオブジェクトを保持できるメモリのチャンクに対して単一の割り当てを実行する方が効率的な場合があります。
DevX が良い例を示しています。
標準 C++ は、事前に割り当てられたバッファー上にオブジェクトを構築する配置 new 演算子もサポートしています。これは、メモリ プールやガベージ コレクタを構築する場合、または単にパフォーマンスと例外の安全性が最も重要な場合に役立ちます (メモリは既に割り当てられているため、割り当て失敗の危険はなく、事前に割り当てられたバッファでオブジェクトを構築するのにかかる時間は短くなります)。 :
char *buf = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi"); // placement new
string *q = new string("hi"); // ordinary heap allocation
また、重要なコードの特定の部分 (たとえば、ペースメーカーによって実行されるコード) で割り当てエラーが発生しないようにすることもできます。その場合、以前にメモリを割り当ててから、クリティカル セクション内で新しい配置を使用します。
プレースメントでの割り当て解除 new
メモリ バッファーを使用しているすべてのオブジェクトの割り当てを解除しないでください。代わりに、元のバッファのみを削除する必要があります。その後、クラスのデストラクタを手動で呼び出す必要があります。これに関する適切な提案については、Stroustrup の FAQ を参照してください: Is there a "placement delete" ?
カスタムメモリプールで使用します。ただのスケッチ:
class Pool {
public:
Pool() { /* implementation details irrelevant */ };
virtual ~Pool() { /* ditto */ };
virtual void *allocate(size_t);
virtual void deallocate(void *);
static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};
class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };
// elsewhere...
void *pnew_new(size_t size)
{
return Pool::misc_pool()->allocate(size);
}
void *pnew_new(size_t size, Pool *pool_p)
{
if (!pool_p) {
return Pool::misc_pool()->allocate(size);
}
else {
return pool_p->allocate(size);
}
}
void pnew_delete(void *p)
{
Pool *hp = Pool::find_pool(p);
// note: if p == 0, then Pool::find_pool(p) will return 0.
if (hp) {
hp->deallocate(p);
}
}
// elsewhere...
class Obj {
public:
// misc ctors, dtors, etc.
// just a sampling of new/del operators
void *operator new(size_t s) { return pnew_new(s); }
void *operator new(size_t s, Pool *hp) { return pnew_new(s, hp); }
void operator delete(void *dp) { pnew_delete(dp); }
void operator delete(void *dp, Pool*) { pnew_delete(dp); }
void *operator new[](size_t s) { return pnew_new(s); }
void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
void operator delete[](void *dp) { pnew_delete(dp); }
void operator delete[](void *dp, Pool*) { pnew_delete(dp); }
};
// elsewhere...
ClusterPool *cp = new ClusterPool(arg1, arg2, ...);
Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);
これで、オブジェクトを 1 つのメモリ アリーナにまとめてクラスタ化し、非常に高速であるが割り当て解除を行わないアロケータを選択し、メモリ マッピングを使用し、プールを選択してそれを引数としてオブジェクトの配置に渡すことで課したいその他のセマンティックを使用できます。新しいオペレーター。
割り当てを初期化から分離したい場合に便利です。STL は、placement new を使用してコンテナー要素を作成します。
リアルタイムプログラミングで使用しました。通常、システムの起動後に動的割り当て (または割り当て解除) を実行することは望ましくありません。
私ができることは、大量のメモリを事前に割り当てることです (クラスが必要とするあらゆる量を保持するのに十分な大きさです)。次に、実行時に物事を構築する方法を理解したら、placement new を使用して、必要な場所にオブジェクトを構築できます。私がそれを使用したことがわかっている状況の 1 つは、異種循環バッファーの作成を支援することでした。
それは確かに気弱な人向けではありませんが、それが彼らがその構文をちょっとぎこちないものにしている理由です。
alloca() を介してスタックに割り当てられたオブジェクトを構築するために使用しました。
恥知らずなプラグ:私はそれについてここにブログを書きました.
ヘッドオタク:ビンゴ!あなたはそれを完全に手に入れました-それはまさにそれが完璧なものです. 多くの組み込み環境では、外部制約および/または全体的な使用シナリオにより、プログラマーはオブジェクトの割り当てとその初期化を分離する必要があります。まとめて、C++ はこれを「インスタンス化」と呼びます。ただし、コンストラクターのアクションを動的または自動割り当てなしで明示的に呼び出す必要がある場合は常に、配置 new がそれを行う方法です。これは、ハードウェア コンポーネント (メモリ マップド I/O) のアドレスに固定されているグローバル C++ オブジェクトや、何らかの理由で固定アドレスに存在する必要がある静的オブジェクトを見つけるのにも最適な方法です。
これはどの回答でも強調されていないと思いますが、別の良い例と新しい配置の使用法は、 (メモリ プールを使用して) メモリの断片化を減らすことです。これは、組み込みシステムや高可用性システムで特に役立ちます。この最後のケースでは、24/365 日実行する必要があるシステムでは、断片化がないことが非常に重要であるため、特に重要です。この問題は、メモリ リークとは関係ありません。
非常に優れた malloc 実装 (または同様のメモリ管理機能) が使用されている場合でも、断片化を長時間処理することは非常に困難です。ある時点で、メモリの予約/解放呼び出しを巧みに管理しないと、再利用 (新しい予約への割り当て) が困難な多くの小さなギャップが発生する可能性があります。したがって、この場合に使用される解決策の 1 つは、メモリ プールを使用して、アプリケーション オブジェクトのメモリを事前に割り当てることです。その後、オブジェクトのメモリが必要になるたびに、新しい配置を使用して、既に予約されているメモリに新しいオブジェクトを作成します。
このように、アプリケーションが起動すると、必要なメモリがすべて確保されます。すべての新しいメモリの予約/解放は、割り当てられたプールに送られます (異なるオブジェクト クラスごとに 1 つずつ、複数のプールがある場合があります)。この場合、メモリの断片化は発生しません。これは、ギャップがなく、システムが断片化に苦しむことなく非常に長期間 (数年) 実行できるためです。
VxWorks RTOS のデフォルトのメモリ割り当てシステムは断片化の影響が大きいため、実際にこれを確認しました。そのため、標準の new/malloc メソッドによるメモリ割り当ては、プロジェクトでは基本的に禁止されていました。すべてのメモリ予約は、専用のメモリ プールに移動する必要があります。
カーネルを構築している場合に便利です。ディスクまたはページテーブルから読み取ったカーネルコードをどこに配置しますか? どこにジャンプするかを知る必要があります。
または、割り当てられた部屋がたくさんあり、いくつかの構造物を互いの後ろに配置したい場合など、非常にまれな状況です。これらは、offsetof() 演算子を必要とせずにこの方法でパックできます。ただし、そのための他のトリックもあります。
また、一部の STL 実装では、std::vector のように新しい配置を利用していると思います。そのように 2^n 要素のスペースを割り当て、常に再割り当てする必要はありません。
グローバルまたは静的に割り当てられた構造を再初期化する場合にも役立ちます。
古い C の方法では、memset()
すべての要素を 0 に設定していました。vtables とカスタム オブジェクト コンストラクターがあるため、C++ ではそれを行うことができません。
だから私は時々次を使用します
static Mystruct m;
for(...) {
// re-initialize the structure. Note the use of placement new
// and the extra parenthesis after Mystruct to force initialization.
new (&m) Mystruct();
// do-some work that modifies m's content.
}
メモリ マップ ファイルを使用してオブジェクトを格納するために使用しました。
特定の例は、非常に多数の大きな画像 (メモリに収まりきらない画像) を処理する画像データベースでした。
通常、 はにあるよりも多くのメモリを割り当てるstd::vector<>
ため、によって使用されます。std::vector<>
objects
vector<>
「動的型」ポインターのわずかなパフォーマンスハックとして使用されているのを見てきました(「ボンネットの下」のセクションで):
しかし、小さな型の高速なパフォーマンスを得るために私が使用したトリッキーなトリックは次のとおりです。保持されている値が void* 内に収まる場合、実際に新しいオブジェクトを割り当てる必要はありません。配置 new を使用してポインター自体に強制します。 .
ネットワークから受信したメッセージを含むメモリに基づいてオブジェクトを作成するために使用しました。
一般に、配置新規は、「通常の新規」の割り当てコストを取り除くために使用されます。
私がそれを使用した別のシナリオは、ドキュメントごとのシングルトンを実装するために、まだ構築されていないオブジェクトへのポインターにアクセスしたい場所です。
他の用途の中でも、共有メモリを使用する場合に便利な場合があります。 conditions_anonymous_example
スクリプトエンジンは、ネイティブインターフェイスでこれを使用して、スクリプトからネイティブオブジェクトを割り当てることができます。例については、Angelscript(www.angelcode.com/angelscript)を参照してください。
私が遭遇した1つの場所は、連続したバッファを割り当て、必要に応じてオブジェクトで埋めるコンテナです。前述のように、std::vector がこれを行う可能性があり、MFC CArray および/または CList のいくつかのバージョンがこれを行ったことを知っています (最初に遭遇した場所であるため)。バッファのオーバーアロケーション メソッドは非常に便利な最適化であり、そのシナリオでオブジェクトを構築する方法は、新しい配置がほぼ唯一の方法です。また、直接コードの外部に割り当てられたメモリ ブロック内にオブジェクトを構築するために使用されることもあります。
頻繁に出てくるわけではありませんが、同様の容量で使用しました。ただし、これは C++ ツールボックスの便利なツールです。
http://xll.codeplex.comにある xll プロジェクトの fp.h ファイルを参照してください。これは、次元を持ち歩くのが好きな配列の「コンパイラとの不当な互換性」の問題を解決します。
typedef struct _FP
{
unsigned short int rows;
unsigned short int columns;
double array[1]; /* Actually, array[rows][columns] */
} FP;