web-dev-qa-db-ja.com

「新規配置」にはどのような用途がありますか?

ここで誰かがC++の「新しい配置」を使用したことがありますか?もしそうなら、何のために?私はそれがメモリマップされたハードウェア上でのみ有用であるように見えます。

370
Head Geek

New placeを使用すると、すでに割り当てられているオブジェクトをメモリ内に構築できます。

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

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 onを見てください: "配置削除"はありますか?

337
Brian R. Bondy

カスタムメモリプールで使用します。ただのスケッチ:

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, ...);

これで、オブジェクトを単一のメモリ領域でクラスタ化し、非常に高速だが割り当て解除を行わないアロケータを選択し、メモリマッピングを使用し、プールを選択してオブジェクトの配置の引数として渡すことで強制したいセマンティックを使用できます。新しいオペレータ.

56
Don Wakefield

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

49
MSN

私はリアルタイムプログラミングでそれを使用しました。システムの起動後、don'tで動的割り当て(または割り当て解除)を実行したいのです。

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

それは確かに気の弱い人のためのものではありませんが、だからこそ彼らはそれのための構文をちょっとばかげているのです。

32
T.E.D.

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

恥知らずなプラグイン:ブログに書きました ここ

23
Ferruccio

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

12
dorcsssc

私はそれを使ってVariantクラス(つまり、単一の値を表すことができるオブジェクト)を作成しました。

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

そのため、代わりに十分な大きさのバイト配列(たとえばsizeof(the_largest_data_type_I_support))を割り当て、Variantがその型の値を保持するように設定されている場合はその場所で適切なC++オブジェクトを初期化します。 (もちろん、異なるPOD以外のデータタイプから切り替える場合は、事前に配置を削除してください)

11
Jeremy Friesner

グローバルまたは静的に割り当てられた構造を再初期化したいときにも便利です。

これまでのCの方法では、すべての要素を0に設定するためにmemset()を使用していました。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.
 }
9
nimrodm

Newを配置すると、シリアル化するときにも非常に便利です(boost :: serializationを使って言うと)。 10年間のc ++では、これは私が初めて配置を必要とした2番目のケースです(インタビューを含める場合は3番目)。

8
RichardBruce

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

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

私はまたいくつかのSTL実装がstd :: vectorのように新しい配置を利用すると信じています。彼らはそのように2 ^ n個の要素のためのスペースを割り当てて、いつもreallocする必要はありません。

8
mstrobl

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

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

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

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

8
rkachach

オブジェクトをメモリマップファイルに保存するために使用しました。
特定の例は、非常に多数の大きな画像を処理した(データベースに収まる以上のもの)画像データベースでした。

7
Martin Beckett

std::vector<>は通常std::vector<>にあるobjectsよりも多くのメモリを割り当てるため、これはvector<>によって使用されます。

7

実際には、挿入される要素の数に最低限必要なものよりも多くのメモリを割り当てる任意の種類のデータ構造(つまり、一度に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);
7
Dragon Energy

私はこれが "動的型"ポインタのわずかなパフォーマンスハック として使われているのを見ました(セクション "Under the Hood"の中で):

しかし、これが私が小さい型のために速いパフォーマンスを得るために使用したトリックなトリックです:保持されている値がvoid *の中に収まることができるならば、 。

7
Max Lybbert

ネットワークから受信したメッセージを含むメモリに基づいてオブジェクトを作成するためにそれを使用しました。

6
Steve Fallows

一般に、プレースメント新規は「通常の新規」の割り当てコストを取り除くために使用されます。

私がそれを使用したもう一つのシナリオは、私がドキュメント毎のシングルトンを実装するために、まだ構築されることになっていたオブジェクトへのポインタへのアクセスを持ちたい場所です。

5
xtofl
5
kralyk

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

4
Raindog

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

私はそれを同じような容量で使用しました、それは頻繁に出ませんが。ただし、C++ツールボックスにとっては便利なツールです。

4
Nick

Xllプロジェクトのfp.hファイルを参照してください。 http://xll.codeplex.com ディメンションを持ち運ぶのが好きな配列の「コンパイラの不備な問題」を解決します。

 typedef struct _FP 
 {
符号なしshort int行; 
符号なしshort int列; 
 double配列[1];/*実際には、array [rows] [columns] */
} FP; 
3
Keith A. Lewis

これが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

2
user2356685