c++ new 上書き - 「プレースメント新規」にはどのような用途がありますか?




11 Answers

カスタムメモリプールで使用します。 ちょうどスケッチ:

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

今度は、単一のメモリ領域内でオブジェクトをクラスタリングし、非常に速いが割り当て解除を行わないアロケータを選択し、メモリマッピングを使用し、プールを選択してオブジェクトの配置に引数として渡すことで、新しい演算子。

delete c++ operator

ここで誰かがC ++の "placement new"を使ったことはありますか? もしそうなら、何のために? メモリマップされたハードウェア上でのみ役に立つと思う。




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

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

それは確かに心の弱い人のためではありませんが、それはそれがちょっとぎこちなく文法を作っている理由です。




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




グローバルまたは静的に割り当てられた構造体を再初期化する場合にも便利です。

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



プレースメントnewは、シリアライズするときにも非常に便利です(boost :: serializationを使って)。 10年のC ++では、これは私が配置を必要とした2番目のケースです(インタビューを含む場合は3番目)。




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




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




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

私が使った別のシナリオは、ドキュメント単位のシングルトンを実装するために、まだ構築されていたオブジェクトへのポインタにアクセスしたいという場です。




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

unordered_mapvectordequeなどのコンテナを使用します。 これらはすべて、挿入するたびにヒープ割り当てを行わないように、これまでに挿入した要素に必要なメモリよりも多くのメモリを割り当てます。 最も単純な例としてvectorを使ってみましょう。

あなたがするとき:

vector<Foo> vec;

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

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

配分!=建設、解放!=破壊

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

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

  • 私はstd::allocator設計が嫌いですが、これは別のテーマです。 :-D

だからとにかく、私はそれを使う傾向があります。なぜなら、既存のものと比較して構築できない汎用的な標準準拠のC ++コンテナをたくさん書いているからです。 その中には、数十年前に一般的なヒープ割り当てやメモリ効率の高いトライ(一度に1つのノードを割り当てない)を避けるために構築した小さなベクトル実装が含まれています。 どちらの場合も、既存のコンテナを使用して実装することはできませんでした。 placement newを使用すると、不要な左右のコンストラクタやデストラクタを余計に呼び出さなくて済むようになりました。

もちろん、カスタムアロケータを使用してオブジェクトを個別に割り当てる場合(空きリストのように)、通常は例外的な安全性やRAIIに悩まされない基本的な例のように、 placement newを使用しplacement new

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



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




ここでは、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




Related