operator new/delete の件

そういえば書くのをすっかり忘れていた。ひげぽんさんの日記(id:higepon:20051102)を読んで思い出したので。
id:arCtan:20051010で自前のメモリ確保を行うためにoperator new/deleteをオーバーロードするplacement new/deleteという手法を使った。そのときに知ったnew/deleteの性質をいくつか。

operator new/delete は継承される

「Effective C++」の8項に書かれていたんですね…。

class Base {
public:
 virtual ~Base();
public:
 static void* operator new(size_t size){/*メモリ確保*/}
 static void  operator delete(void*p){/*メモリ解放*/}
};

class Derive : public Base {
};

Base* p = new Derive;
delete p;

こうすると、

Base::operator new(sizeof(Derive))
Base::operator delete(p)

が呼び出される。したがって、Base::operator newがBaseクラスの仕様にあわせたメモリ確保を行っている場合はヤバイよ。と書いてある(^^;
自分はこのBase::operator newを、sizeすなわちsizeof(Derive)の大きさでメモリを確保するように設計していたので、特に問題はなかった。

基底クラスのoperator deleteが掩蔽される

「Effective C++」に書かれているようで書かれてない?
なぜか実際ぶち当たった次のような例。

//改変できないインターフェース
class Interface {
public:
 virtual ~Interface();
};

//自分で作った親クラス
class Base : public Interface {
public:
 virtual ~Base();
public:
 static void* operator new(size_t size){/*メモリ確保*/}
 static void  operator delete(void*p){/*メモリ解放*/}
};

//自分で作った子クラス
class Derive : public Base {
};

Interface* p = new Derive;
delete p;

つまり、もともとあったインターフェースを使わなければならないが、メモリ管理の方法は変更したいという事例である。こんなこと普通ないか…。まあとりあえず、やってみるとどうなるのか。
まず始めの生成のところは、仮想的に書くと、

p = Base::operator new(sizeof(Derive));
p->Interface::Interface();
p->Base::Base();
p->Derive::Derive();

という感じだろう。特に問題はないはずだ。問題がありそうなのは解体のほうだ。まず

p->Derive::~Derive();
p->Base::~Base();
p->Interface::~Interface();

とデストラクタが呼ばれるだろう。それからoperator deleteが呼ばれるはずだが、はてさて、どのoperator deleteが呼ばれるのか?pはInterface型へのポインタで、operator deleteは仮想関数ではないのだから、Interface::operator delete(定義されていないのでグローバルの::operator delete)が呼ばれると考えるのが自然ではないか?そしてBase::operator deleteは呼ばれず、new/deleteの不整合が起きるのではないか?
ところが実際やってみるとあら不思議。ちゃんとBase::operator deleteが呼ばれて正しくメモリが解放される。
なんでやねん。という感じだが、今回のケースに限ってはこういう振る舞いをしてくれたほうが助かることは助かる。
詳しい理由はよくわからないけど、わかったら書きます。