C++でオブジェクト指向をおさらい中
注:このコードには失敗があって後で修正されますが、何につまずいたかを記録しておくために間違ったまま載せてます
#include<iostream> //#define A //#define B class O{ public: int f(){ return 0; } }; class A : public O{ #ifdef A public: int f(){ return 1; } #endif }; class B : public O{ #ifdef B public: int f(){ return 2; } #endif }; class C : public A, public B{ }; int main(){ C c; std::cout << c.f() << std::endl; }
まず、AもBもオーバーライドしない状態で2つのO::fが衝突してambiguousになる
multi.cpp:36: error: request for member ‘f’ is ambiguous multi.cpp:8: error: candidates are: int O::f() multi.cpp:8: error: int O::f()
Oのインスタンスを1つだけにするにはA, Bがそれぞれvirtual継承する必要がある。virtualをつけるとO::fが呼ばれて0と表示される。じゃあAだけつけてみると…
multi.cpp:13: error: an anonymous union cannot have function members multi.cpp:20: error: abstract declarator ‘<anonymous class>’ used as declaration multi.cpp:31: error: expected class-name before ‘,’ token
む。よくわからないことになってしまった。
はっ、マクロの名前とクラスの名前が衝突するのか!修正。Aだけ付けると1になる。あたりまえ。Bだけつけると2になる。では両方付けると?
multi.cpp:36: error: request for member ‘f’ is ambiguous multi.cpp:25: error: candidates are: int B::f() multi.cpp:16: error: int A::f()
やっぱりambiguous。ここまでは直感に反しない。どういうときに直感に反しそうか…うーん。思いつかないぞ?Cでfを定義したら当然上書きするだろうし。
http://ml.tietew.jp/cppll/cppll/article/3588
うーむ。当然topが呼ばれるだろう、と思ってそのとおりに動いたのだが、スレッドを追っているとrightだと書いてあって、あれれ?と思ったらBaseのfにvirtualが付いているのね。virtual関数ってなんだっけ、とググって1番上に来たページの説明のほうがよっぽど直感に反したものだったのでスルーして、帰ってまともな本を読むことにしよう。
まあ挙動からの予想では、virtualのついていないO::fはOの中に埋め込まれていて、これはBでオーバーライドされる際にBの中ではB::fが呼ばれるようなレイアウトになるけども、Oの実体が書き換えられるわけではない。だって他でOを使っているところがあったら困るもんね。で、virtualの付いているO::fの場合はvtableに入っていて「fを呼ぶときにはvtableを参照せよ」という情報がAにも伝わっている。で、c.g()と、Cの型でA::gを呼ぶと、Cのvtableを参照しに行くのでfはB::fになる。試しに A a = c;してからa.g()するとO::fが呼ばれている。
とここまで書いて帰ろうと思ったがプログラマの美徳の3番目「人に間違いを指摘されるのが許せない『傲慢』」が発動して確認のためにコードを書いたのであった:
#include<iostream> using namespace std; //#define VIRTUAL class O{ public: #ifdef VIRTUAL virtual #endif void f(){ cout << "Base" << endl; } }; class A : public virtual O{ public: void g(){ f(); } }; class B : virtual public O{ public: void f(){ cout << "B" << endl; } }; class C : public A, public B{ }; int main(){ C c; c.g(); // when VIRTUAL: B otherwise BASE A a = c; a.g(); // BASE }
virtual継承でない場合にはそもそもAとBが共通のOへの参照を持つのではなく、AやBそれぞれ自分のOのコピーを持っているので当然BASEになるよね。