「新しいプレースメント」にはどのような用途がありますか?


411

C ++の「プレースメント新品」を使った人はいますか?もしそうなら、何のために?メモリマップされたハードウェアでのみ役立つように思えます。


14
これは、ブースト割り当てメモリプールでオブジェクトコンストラクターを呼び出すために、私が探していた情報です。(これらのキーワードを希望すると、将来誰かが見つけやすくなります)。
サイドショーボブ2011

2
ユニオンのコンストラクターのC ++ 11 Wikipediaの記事で使用されています。
HelloGoodbye 2015年

@HelloGoodbye、面白い!あなたがリンクした記事で、なぜあなたp = ptPoint代わりに代入演算子を使用して使用できないのですnew(&p) Point(pt)か?両者の違いが気になります。前者operator=はPointを呼び出し、後者はPointのコピーコンストラクタを呼び出しますPointか?しかし、なぜ一方が他方よりも優れているのかは、まだはっきりしていません。
Andrei-Niculae Petre

@ Andrei-NiculaePetre自分で配置newを使用したことはありませんが、現在そのクラスのオブジェクトがない場合は、コピーコンストラクターと共に使用する必要があります。それ以外の場合は、コピー代入演算子を使用する必要があります。クラスが些細な場合を除き、どちらを使用してもかまいません。オブジェクトの破壊についても同様です。重要なクラスでこれを適切に処理しないと、奇妙な動作が発生する可能性が高く、状況によっては未定義の動作が発生することもあります。
HelloGoodbye

@ Andrei-NiculaePetre実際には、ウィキペディアの記事のはかなり悪いと思います。これは、前のオブジェクトが存在せず、オブジェクトを作成する必要があると仮定しているためです。U::operator=呼び出されたばかりの場合、これは当てはまりません。
HelloGoodbye

回答:


365

新しい配置により、すでに割り当てられているオブジェクトをメモリ内に構築できます。

オブジェクトの複数のインスタンスを作成する必要があり、新しいインスタンスが必要になるたびにメモリを再割り当てしない方が速い場合は、最適化のためにこれを行うことができます。代わりに、一度にすべてを使用したくない場合でも、複数のオブジェクトを保持できるメモリのチャンクに対して単一の割り当てを実行する方が効率的です。

DevXは良い例です:

標準C ++は、事前に割り当てられたバッファにオブジェクトを構築する配置新しい演算子もサポートします。これは、メモリプール、ガベージコレクターを構築する場合、または単にパフォーマンスと例外の安全性が最重要である場合に便利です(メモリが既に割り当てられているため、割り当てに失敗する危険はなく、事前に割り当てられたバッファーにオブジェクトを構築するのにかかる時間は短くなります)。 :

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を参照してください:「プレースメントの削除」はありますか?


54
コンテナーオブジェクト(ベクターなど)を効率的に実装するためにこの機能が必要なため、非推奨ではありません。独自のコンテナを構築していない場合は、この機能を使用する必要はありません。
マーティンヨーク

26
#include <memory>を覚えておくことも非常に重要です。そうしないと、新しい配置を自動的に認識しない一部のプラットフォームでひどい頭痛が発生する可能性があります
Ramon ZarazuaB。09年

22
厳密delete[]には、元のcharバッファを呼び出すのは未定義の動作です。配置newchar使用すると、ストレージを再利用することで元のオブジェクトの寿命が終了しました。delete[] buf指されたオブジェクトの動的タイプを呼び出すと、静的タイプと一致しなくなるため、未定義の動作になります。operator new/ operator deleteを使用して、配置で使用するために意図されたrawメモリを割り当てる方が一貫していますnew
CBベイリー

31
私は間違いなくペースメーカーでヒープを使用することをスキップします:-)
Eli Bendersky

15
@RamonZarazuaヘッダーが間違ってい#include <new>ます。
bit2shift 2016

63

カスタムメモリプールで使用します。単なるスケッチ:

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
うん。それについてはかなり賢明ですが、この質問ではトピックから外れています。
Don Wakefield

2
@jdkoftinoff実際のコードサンプルへのリンクはありますか?私にとってはかなり興味深いようです!
ビクター

@DonWakefieldこのプールでの配置をどのように処理しますか?アライメントを引数としてallocate()どこかに渡すべきではありませんか?
ミハイルヴァシリエフ2018年

1
@MikhailVasilyev、実際の実装では、もちろんそれを処理します。サンプルコードのみ。
Don Wakefield

配置が無効なアドレス、たとえば0x0の場合はどうなりますか?
チャーリー、

51

割り当てと初期化を分離したい場合に便利です。STLは新しい配置を使用してコンテナー要素を作成します。


35

リアルタイムプログラミングで使用しました。通常、システムの起動後に動的割り当て(または割り当て解除)を実行する必要はありません。これには、かかる時間が保証されていないためです。

私ができることは、メモリの大きなチャンクを事前に割り当てることです(クラスが必要とするものをいくらでも保持するのに十分な大きさ)。次に、実行時に物事を構築する方法を見つけたら、新しい配置を使用して、必要な場所にオブジェクトを構築できます。私がそれを使用したことがわかっている1つの状況は、異種混合循環バッファーの作成を支援することでした。

それは確かに心臓の弱い人のためではありませんが、それが彼らがそのための構文をちょっと危険にしてしまう理由です。


TEDさん、こんにちは。あなたがお持ちのソリューションについて詳しく教えてください。私は事前に割り当てられたソリューションについて考えていますが、あまり進歩していません。前もって感謝します!
Viet

1
さて、実際のヘテロジニアスな循環バッファーコードは、正しく理解するのが難しい部分でした。新しいpalcementは少し恐ろしく見えますが、比較すると全く問題ありませんでした。
TED

26

alloca()を使用してスタックに割り当てられたオブジェクトを構築するために使用しました。

恥知らずなプラグイン:私はそれについてここでブログに書い


興味深い記事ですが、これを使用する利点を理解できているかどうかはわかりませんboost::array。あなたはそれを少し拡張できますか?
GrahamS、2011

boost :: arrayでは、配列のサイズをコンパイル時の定数にする必要があります。これにはその制限はありません。
Ferruccio

2
@Ferruccioこれはかなりかっこいいですが、マクロが少し安全ではないことに気づきました。つまり、サイズが問題になる可能性があります。たとえばx + 1が渡された場合、それをsizeof(type)* x + 1に展開しますが、これは正しくありません。安全にするために、マクロを括弧で囲む必要があります。
ベンジ

allocaで使用すると、すべてのオブジェクトでデストラクタを呼び出さなければならないために例外がスローされた場合、私には危険に見えます。
CashCow 2013

14

ヘッドオタク:ビンゴ!あなたはそれを完全に手に入れました-それはまさにそれが完璧なものです。多くの組み込み環境では、外部制約や全体的な使用シナリオにより、プログラマーはオブジェクトの割り当てを初期化から切り離す必要があります。まとめると、C ++はこれを「インスタンス化」と呼びます。ただし、動的または自動割り当てなしでコンストラクターのアクションを明示的に呼び出す必要がある場合は常に、新しい配置がその方法です。また、ハードウェアコンポーネント(メモリマップI / O)のアドレスに固定されているグローバルC ++オブジェクト、または何らかの理由で固定アドレスに存在する必要がある静的オブジェクトを見つけるのに最適な方法でもあります。


12

私はそれをVariantクラス(つまり、いくつかの異なるタイプの1つになることができる単一の値を表すことができるオブジェクト)を作成するために使用しました。

Variantクラスでサポートされているすべての値型がPOD型(int、float、double、boolなど)の場合、タグ付きのCスタイルの共用体で十分ですが、一部の値型をC ++オブジェクトにする場合(例えばstd :: string)の場合、非PODデータ型は共用体の一部として宣言されない可能性があるため、Cの共用体機能は機能しません。

したがって、代わりに、十分な大きさのバイト配列(sizeof(the_largest_data_type_I_support)など)を割り当て、Variantがその型の値を保持するように設定されている場合は、配置newを使用してその領域の適切なC ++オブジェクトを初期化します。(もちろん、別の非PODデータ型から切り替える場合、配置は事前に削除されます)


ええと、非PODデータ型ユニオン内で宣言できますが、ユニオンのctorを提供する限り、そのctor はおそらく配置new使用して非PODサブクラスを初期化します。参照:stackoverflow.com/a/33289972/2757035任意の大きなバイト配列を使用してこのホイールを再発明することは、アクロバットの印象的な部分ですが、完全に不要であるように見えます。:)
underscore_d

6
C ++ 11より前のすべてのバージョンのC ++を見逃しました。多くの場合、まだサポートする必要があります。:)
Jeremy Friesner、2015年

10

新しい配置は、シリアライズするときにも非常に便利です(たとえば、boost :: serializationで)10年間のc ++では、これは私が新しい配置を必要とした2番目のケースにすぎません(インタビューを含めると3番目):))。


9

また、グローバルまたは静的に割り当てられた構造を再初期化する場合にも役立ちます。

古いCの方法では、memset()すべての要素を0に設定するために使用していました。C++では、vtablesとカスタムオブジェクトコンストラクターのため、これを行うことはできません。

だから私は時々以下を使います

 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.
 }

1
そのように再初期化する前に、対応する破棄を行う必要はありませんか?
オタクのヘッド

[スペルチェック用に編集]通常-行います。ただし、クラスがメモリやその他のリソースを割り当てていないことがわかっている場合(またはメモリプールを使用する場合など、それらを外部で割り当て解除した場合)は、この手法を使用できます。v-tableポインタが上書きされないことが保証されます。– nimrodm 16時間前
nimrodm 2013

1
Cでさえ、すべてのビットを0に設定すると、他の型ではなく整数型の0の表現を生成することが保証されます(ヌルポインターは非ゼロの表現を持つことができます)。
curiousguy

@curiousguy-プリミティブ型の場合、あなたは正しいです(プログラムを予測可能にします。これは、デバッグに関しては利点です)。ただし、C ++データ型のコンストラクターは(インプレースで)実行され、適切に初期化されます。
nimrodm

9

これはどの回答でも強調されていないと思いますが、新しい配置の別の良い例と使用法は、(メモリプールを使用して)メモリの断片化を減らすことです。これは、組み込みシステムおよび高可用性システムで特に役立ちます。この最後のケースでは、24/365日稼働する必要のあるシステムでは断片化がないことが非常に重要であるため、これは特に重要です。この問題は、メモリリークとは関係ありません。

非常に優れたmalloc実装(または同様のメモリ管理機能)が使用されている場合でも、断片化を長期間処理することは非常に困難です。ある時点で、メモリの予約/解放の呼び出しを巧みに管理しないと、再利用(新しい予約への割り当て)が困難な小さなギャップが多数発生する可能性があります。したがって、この場合に使用されるソリューションの1つは、メモリプールを使用して、アプリケーションオブジェクトのメモリを事前に割り当てることです。その後、オブジェクトのメモリが必要になるたびに、新しい配置を使用して、すでに予約されているメモリ上に新しいオブジェクトを作成します。

このように、アプリケーションが起動すると、必要なすべてのメモリがすでに予約されています。すべての新しいメモリの予約/解放は、割り当てられたプールに行きます(オブジェクトクラスごとに1つずつ、複数のプールがある場合があります)。この場合、メモリの断片化は発生しません。ギャップが発生せず、システムは断片化の影響を受けることなく非常に長い期間(数年)実行できるためです。

VxWorks RTOSの場合、デフォルトのメモリ割り当てシステムは断片化の影響を大きく受けるため、これを実際に見ました。したがって、プロジェクトでは、標準のnew / mallocメソッドを使用してメモリを割り当てることは基本的に禁止されていました。すべてのメモリ予約は、専用のメモリプールに移動する必要があります。


9

実際には、挿入される要素の数に最小限必要なメモリよりも多くのメモリを割り当てるあらゆる種類のデータ構造(つまり、一度に1つのノードを割り当てるリンク構造以外のもの)を実装する必要があります。

以下のようなコンテナを取るunordered_mapvectorまたはdeque。これらはすべて、1回の挿入ごとにヒープ割り当てを必要としないようにするために、これまでに挿入した要素に最低限必要なメモリよりも多くのメモリを割り当てます。vector最も単純な例として使用してみましょう。

あなたがするとき:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

...実際に1000のFoosを構築するわけではありません。単にメモリを割り当てる/予約するだけです。vectorここで新しい配置を使用しなかった場合、それはFoos場所全体にデフォルトで構築され、最初の場所に挿入したことのない要素に対してもデストラクタを呼び出す必要があります。

割り当て!=構築、解放!=破壊

上記のような多くのデータ構造を実装するために一般的に言えば、メモリの割り当てと要素の構築を1つの不可分なものとして扱うことはできません。同様に、メモリの解放と要素の破壊を1つの不可分なものとして扱うこともできません。

コンストラクタとデストラクタを不必要に左右に呼び出さないようにするには、これらのアイデアを分離する必要があります。そのため、標準ライブラリはstd::allocator(メモリの割り当て/解放時に要素を構築または破棄しない*)というアイデアをそれを使用するコンテナは、配置newを使用して要素を手動で構築し、デストラクタの明示的な呼び出しを使用して要素を手動で破棄します。

  • 私はデザインが嫌いですstd::allocatorが、それは私が怒って避けるのは別の主題です。:-D

とにかく、既存のものでは構築できない汎用の標準準拠のC ++コンテナーを多数作成したので、とにかく私はそれを頻繁に使用する傾向があります。その中に含まれているのは、一般的なケースでのヒープ割り当てを回避するために数十年前に作成した小さなベクトル実装と、メモリ効率の良いトライ(一度に1つのノードを割り当てない)です。どちらの場合も、既存のコンテナーを使用してそれらを実際に実装することはできなかったため、placement new左と右の不要なものに対してコンストラクターとデストラクターを過剰に呼び出すことを避けるために使用する必要がありました。

もちろん、カスタムリストを使用してオブジェクトを個別に割り当てる場合(フリーリストなど)、通常placement newは次のようにを使用することもできます(例外安全性やRAIIを気にしない基本的な例):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);

8

カーネルを構築している場合に便利です。ディスクまたはページテーブルから読み取ったカーネルコードはどこに配置しますか?ジャンプ先を知る必要があります。

または、割り当てられた部屋がたくさんあり、いくつかの構造物を互いに後ろに配置したい場合など、非常にまれな状況です。これらは、offsetof()演算子を必要とせずに、この方法でパックできます。ただし、他にもトリックがあります。

また、STD :: vectorのように、いくつかのSTL実装が新しい配置を利用していると思います。それらは2 ^ n要素のスペースをそのように割り当て、常に再割り当てする必要はありません。


メモリ割り当ての削減は、それを使用する主な理由の1つであり、ディスクからオブジェクトをロードするような「トリック」
lefticus

C ++で書かれたカーネルは知りません。ほとんどのカーネルはまっすぐCで書かれている
アダムローゼンフィールドを

8
私がOSの基本を学んだオペレーティングシステムはC ++で書かれています: sweb.sourceforge.net
mstrobl


7

私は、メモリマップファイルを含むオブジェクトを格納するために使用しました。
具体的な例は、大量の大きな画像(メモリに収まりきらない)を処理する画像データベースでした。


7

私はそれが「動的タイプ」ポインタ(セクション「フードの下」)のわずかなパフォーマンスハックとして使用されるのを見てきました。

しかし、ここで私が小さなタイプの高速パフォーマンスを取得するために使用したトリッキーなトリックがあります:保持されている値がvoid *の内側に収まる場合、実際には新しいオブジェクトの割り当てを気にせず、配置newを使用してそれをポインター自体に強制します。


保持されている値がvoid *の内側に収まる場合どうなりますか?ポインタ型をvoid *に割り当てることは常に可能です。いくつか例を示していただけますか?
anurag86 2015年

@ anurag86:私の64ビットマシンでは、a void*は8バイトかかります。8バイトvoid*を1 バイトにポイントするのは少しばかげていますbool。しかし、それは実際にオーバーレイすることは完全に可能だboolvoid*のような、非常にunion { bool b; void* v }。呼び出したものvoid*が実際にbool(またはshort、またはfloatなど)であることを知るには、何らかの方法が必要です。私がリンクした記事は、その方法を説明しています。そして、元の質問に答えるために、配置newboola void*が期待される場所(または他のタイプ)を作成するために使用される機能です(キャストは後で値を取得/変更するために使用されます)。
Max Lybbert、

@ anurag86:同じではありませんが、タグ付きポインターに興味があるかもしれません(en.wikipedia.org/wiki/Tagged_pointer)。
Max Lybbert


5

一般に、新しい配置は、「通常の新規」の割り当てコストを取り除くために使用されます。

私がそれを使用した別のシナリオは、ドキュメントごとのシングルトンを実装するために、まだ作成されていないオブジェクトへのポインターにアクセスしたい場所です。



4

私がそれに遭遇した1つの場所は、連続したバッファーを割り当て、必要に応じてオブジェクトでそれを埋めるコンテナーです。前述のように、std :: vectorがこれを実行する可能性があり、MFC CArrayやCListの一部のバージョンがこれを実行したことを知っています(これが私が最初に遭遇した場所だからです)。バッファーの過剰割り当て方法は非常に便利な最適化であり、新規配置は、そのシナリオでオブジェクトを構築するためのほとんど唯一の方法です。また、直接コードの外部に割り当てられたメモリブロック内のオブジェクトを構築するために使用されることもあります。

あまり出てこないのですが、同じくらいの容量で使いました。ただし、C ++ツールボックスには便利なツールです。


4

スクリプトエンジンは、それをネイティブインターフェイスで使用して、スクリプトからネイティブオブジェクトを割り当てることができます。例については、Angelscript(www.angelcode.com/angelscript)を参照してください。


3

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;

2

C ++のインプレースコンストラクターのキラーな使用法は次のとおりです。キャッシュラインと2つの境界のその他の累乗に合わせます。これが、5以下のシングルサイクル命令で2のべき乗の境界に対応する超高速ポインタアライメントアルゴリズムです

/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

今、それはあなたの顔を笑顔にするだけではありません(:-)。私♥♥♥C ++ 1x

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.