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になるよね。