水と油を混ぜる作業

唐突ですが、速度をまともなレベルに上げないといけないなと思いまして、ポリモーフィズムとメモリの事前確保&使い回しを両立させるシステムを作っておるわけです。
はじめはむーんどうしたものか…って感じでしたが、調べるとplacement newという手法を使うのが有力なようです。これはoperator newをオーバーロードして、本来newがヒープから引っ張ってくる新メモリを、既存のメモリで代替させようという方針です。こういうのが入門書には載ってない脱初心者向け技術って気がします。さてplacement newは、ただどっかで静的に確保したメモリを渡して、それをそのまま返せばいいだけなので、以下のような実装になるようです。

class CHoge {
public:
 //placement new
 static void* operator new(size_t size, void* buf){ return buf; }
 //対になるplacement delete。くわしくは後述
 static void  operator delete(void*p){}
};

具体的な使い方は、

char buffer[sizeof(CHoge)];
CHoge* p = new(buffer) CHoge;

これに固定領域をいろいろやりくりしてくれるyaneSDKのCMemoryAllocatorPoolクラスを使い、

BYTE buffer[1024]        //静的事前確保
CMemoryAllocatorPool pool;
pool.SetMemory(&buffer[0],1024);
CHoge* p = new(pool.Alloc(sizeof(CHoge))) CHoge;    //newに引数でメモリをわたす

とやると、placement newが実現。newに引数がついてますねえ。このへんわかんない人(昨日までの俺)は下で詳しく。
さて、これにポリモーフィズムを加えます。

class IHoge {
public:
 virtual ~IHoge(){}
 static void* operator new(size_t size, void* buf){ return buf; }
 static void  operator delete(void* p){}
};

class CHoge : public IHoge {
};

で、

IHoge* p = new(pool.Alloc(sizeof(CHoge))) CHoge;
//なんやかんややる
delete p;
pool.Free((BYTE*)p);

これでいいはずなんですが、いくつもオブジェクトを生成したり解体したりとばらばらやってると、ある条件でおかしくなります。具体的にはメモリの二重解放が起きたり、オブジェクトのメンバが不正に書き換えられたり。調べてみたところ、後者はAllocするときにまだ解放してないはずの領域に踏み込んでるようです。前者もこれに絡んだことだと思うがよくわからず。原因は現在調査中。

はいここで初心者の俺が来ましたよメモ

new、delete、オブジェクトの生成解体、メモリの確保解放らへんの話。
まず名著"More Effective C++"の項目8に書かれているように、「new演算子」と、「operator new」は別物である。後者はオーバーロード可能であるが、前者はできない。この二つの何が違うのか、結論からいうと「new演算子」は「operator new」を含んでいる。「new演算子」は、そのオブジェクトに必要なサイズのメモリを「operator new」を使って確保し、そのメモリに対し、コンストラクタを呼び出す。「operator new」は、そのオブジェクトのサイズのメモリを返す、という動作だけ保証しておけばよく、その他の動作をさせるかどうかはオーバーロードする人にゆだねられる(普通は必要ない)。
また、「operator new」を直に呼ぶことは普通はない。はじめ勘違いしかけたが、上で呼んでいるのはオーバーロードした「operator new」ではなく、「new演算子」である。「new演算子」は

new 型名

という特殊な書き方ゆえにオーバーロードできないんだなたぶん。上で書いたように、もしオーバーロードで「operator new」に引数を追加したなら、「new演算子」にもその引数を指定できる。「operator new」を直に呼ぶときは、

CHoge* p = CHoge::operator new(sizeof(CHoge),buffer);

という感じになる。
次に、メモリの確保解放と、コンストラクタ・デストラクタの話。
メモリの確保と、コンストラクタ呼び出し。これらはどちらもオブジェクトの生成に関わる話なので、ともすると俺のような初心者は同じものだと思いがち。全然違います。普通にオブジェクトを作ろうとして、

CHoge hoge;

ってやったり、

new CHoge;

とかすると二つが同時に行われるので混同しちゃうんだね。
メモリの確保とコンストラクタ呼び出しは別々に行える。前者はmalloc()を直に呼んだり、1バイトのchar型を必要数宣言したりで行える。後者は直接はできないけど、new演算子を使うと暗黙に行われるので、「operator new」を(新しいメモリを確保しないように)オーバーロードしてからnew演算子を呼べばよい。もちろんコンストラクタを適用するメモリ領域は必要なので、前者で独立確保したメモリを割り当てる。このように、オブジェクト生成時の二つのプロセスを分けて考えてこそのplacement newのアイディアというわけだ。
そしてこれまでの話と逆に、「delete演算子」はデストラクタを呼び出してからメモリの解放を行う。だから冒頭のソースにあるように、ヒープへのメモリの解放をせずデストラクタ呼び出しだけを行うために、「operator delete」をオーバーロードするplacement deleteも同時に必要になるわけ。
そういえば、メモリの確保とコンストラクタ呼び出しは全然違うプロセスなのに、今までこの違いを全く意識してこなかった。さすがにプログラマ志望としてやばいと思うので子供にもわかるようにメモっとく。
メモリの確保はオブジェクトの居場所を作ること。コンストラクタはオブジェクトの中身を組み立てること。逆にデストラクタはオブジェクト中身をすべて片付けること。メモリの解放は他のオブジェクトのために場所をあけること。
デストラクタを呼んでもメモリを解放しなければ、そこには次のオブジェクトを作れない。デストラクタを呼ばずにメモリを解放すると、部屋を片付けずに次の住人に明渡すようなもので、部屋に残った物を変にいじられる、つまりメンバへの不正アクセスが起きる可能性がある。メンバを持たない組み込み型は、デストラクトせずに解放しても実質的な問題はない、のかな?


超参考元
http://www.fides.dti.ne.jp/~oka-t/cpplab-placement-new.html
http://www1.kcn.ne.jp/~robe/cpphtml/html03/cpp03049.html