高林さんのとこでチラっと出ていたコンストラクタの実体が二つある件について。
http://namazu.org/~satoru/blog/archives/000061.html
コンストラクタ Tax::Tax() が 2つあるのは、片方はオブジェクトの作成用、もう片方は継承されたとき用のようです。
ここに理由が書いてあります!(たぶん)
http://www.codesourcery.com/cxx-abi/abi.html#vtable-ctor
読んでもイマイチわからんというか面倒そうなので正直読んでません、が、なんとなく理由はわかった気がしました。
例えばこんなコード。
struct A { A(); }; struct B : public A { B(); }; struct C : public B { C(); };
逆アセしてやると、 C::C() では B::B() を呼び、 B::B() は A::A() を呼んでました。二つのコンストラクタともにやってることは一緒でした。これはなんの問題もないため、コンストラクタ二つもイラネーと苦情が出るのでした。
でもこのケース。
struct A { A(); }; struct B : public virtual A { B(); }; struct C : public virtual A, public virtual B { C(); };
このケース、多重継承なので C::C() が呼ぶべきコンストラクタは A::A() と B::B() の二つ。これで B::B() まで A::A() を呼び出していた場合、 C::C() から A::A() が合計二度呼び出されてしまうわけでして。そんなわけでこの場合は B::B() と C::C() の二種類目のコンストラクタは内容が変わっていて、それぞれ親クラスのコンストラクタを呼ばないようになっています。子クラスから呼ぶ場合はそっちのコンストラクタを呼ぶことによって、 A::A() が二度呼ばれることを回避してるみたいです。
まとめると
- 最近の g++ は二つコンストラクタの実体を作る
- 片方(1)は外部から、もう片方(2)は子クラスのコンストラクタから呼ばれる
- (1) の方は親クラスのコンストラクタを全て呼ぶ
- (2) の方は virtual 継承でない親クラスのコンストラクタを全て呼ぶ
といった感じ。
そこで気になるのがこの ABI 変更がある前。山程 GCC が入ってる意味が久々にありますよ、っと GCC-2.95.4 でコンパイルして逆アセしてみると、なんか引数無いコンストラクタなのに引数取ってないかこれ、とシンボル見てみると、
080484e8 T A::A(void) 08048508 T B::B(int) 0804854c T C::C(int)
ダサい。勝手に引数が加わってやがります。呼ばれてるコンテキストによってフラグを渡して、それで動作を分岐している模様。 ICC でもやってみたところ、合計3種類のコンストラクタが生成されていて、うち1つがフラグを必要とする外部に隠蔽されているコンストラクタで、残り2つがその隠しコンストラクタを違う引数で呼び出すコンストラクタでした。つまりコンストラクタが大きい時に同じコンストラクタを二つ作るとサイズが大きくなってしまうからこうしてるのだと思います。
ていうか最近の g++ はデカいコンストラクタでも遠慮なく二つ作りやがるのでコンストラクタは小さくしようということかこれは。
あと気になったことは、二つ実装があるってことはデバッガどうなるん?と。
(gdb) break abi.cc:13 Breakpoint 1 at 0x8048468: file abi.cc, line 13. Breakpoint 2 at 0x804848a: file abi.cc, line 13. warning: Multiple breakpoints were set. warning: Use the "delete" command to delete unwanted breakpoints.
ちゃんと両方止めてくれました。
ていうか問題は、「俺は virtual 継承使わんからコンストラクタは一個にしやがれ!」ってオプションが無いように見えることか…
さらにどうでもいい話題に…
#ifdef VIRTUAL struct A { A(int i); }; struct B : public virtual A { B(int i); }; struct C : public virtual A, public virtual B { C(int i); }; #else struct A { A(int i); }; struct B : public A { B(int i); }; struct C : public B { C(int i); }; #endif A::A(int i) { printf("A%d\n", i); } B::B(int i) : A(i+1) { printf("B%d\n", i); } C::C(int i) : #ifdef VIRTUAL A(i+1), #endif B(i+1) { printf("C%d\n", i); } int main() { A a(0); B b(0); C c(0); return 0; }
あたりまえだけど、 -DVIRTUAL つけたりつけなかったりで動作変わるのね…
> g++ abi.cc > ./a.out A0 A1 B0 A2 B1 C0 > g++ abi.cc -DVIRTUAL > ./a.out A0 A1 B0 A1 B1 C0