超久々にポインタで嵌った。
ファクトリメソッドな部分のコードで...
struct hoge { ... }; class hige :public hoge { ... };
...てな具合で前提条件として hoge 構造体と hoge 構造体を継承した hige クラスがありますよと。で、この hige クラスに相当する部分がファクトリメソッドで生成するオブジェクトなんだけど...
templatevoid create_hoge(hoge **x, ...) { *x = malloc(sizeof(T)); new (*x) T(...); } void make_hoge(hoge **x, ...) { ... create_hoge (x, ...); ... }
...のようなコードで作ろうとして嵌りました。問題となったのは...
*x = malloc(sizeof(T)); new (*x) T(...);
この2行です。実際のコードでは意味があって配置構文 new を使ってたわけですが、このコードを...
*x = new T(...);
...のようにしていれば問題ありませんし、配置構文 new を使った形でも最初は問題がありませんでした。実際に問題が発生したのは hige クラスに仮想関数を加えてからです。コンパイラにもよるのですが、vc, icl, g++ などのコンパイラでは POD な構造体・クラス(hoge 構造体)を継承した仮想関数を持つクラス(hige クラス)のポインタは親クラス(hoge 構造体)のポインタに変換すると物理的なアドレスが変わってしまいます(hoge 構造体よりも前に vtbl が配置されている為)。ところが上記のコードでは親クラス(hoge 構造体)のポインタ *x が指すアドレスにダイレクトに配置構文 new で継承したクラス(hige クラス)を構築し、*x が指すアドレスには確かに hige クラスが作成されるものの、それは本来 *x がポイントするべきアドレスではありません。
で、結局...
*x = malloc(sizeof(T)); new (*x) T(...); *x = *(T*)x;
...のようにずれているアドレスを調整する一文を加えて解決しました、少々不格好ですけど。メモリの解放については hige クラスのメンバ関数内で行っている(自殺)ので特に元のアドレスに戻すといった類の修正の必要はありませんでした。
ちなみに bcc では vtbl が POD な親クラスの後ろにレイアウトされる為に今回のような問題は起きないようです。