チラシの裏は意外と白くない

最近忘れっぽくなったので調べたことをチラシの裏に書きます

クラスに関して④:仮想関数のメリットとおもわれること

昨日、仮想関数のありがたみがよくわからないという話を書いた。

  1. 親クラスでvirtual void sing()とvoid fly()を用意
  2. 親クラス型のポインタを宣言
  3. 親クラス型のポインタに子クラスをインスタンス
  4. sing()は子クラスのsing()が呼ばれるが、fly()は親クラスのfly()が呼ばれてしまう

じゃあ最初から素直に子クラス型のポインタを宣言して、インスタンスすればいいじゃないか、という内容。実際そうすればうまくいく。

ちょっと例を変えて、main.cppに以下の関数を追加。

void waiting(CBird* bird){
    bird->sing();
}

void throwing(CBird* bird){
    bird->fly();
}

何だかわからないけどとにかくbirdクラスを受け取ったら、それを鳴かせる関数と飛ばせる関数を定義した。 main分で以下の3パターンの動作を確認。

Bird型ポインタにBirdコンストラク

    CBird* b0;
    b0 = new CBird();

    waiting(b0);
    throwing(b0);

結果は

Birds sing
Birds fly

これはOK

Bird型ポインタにCrowコンストラク

    CBird* b1;
    b1 = new CCrow();

    waiting(b1);
    throwing(b1);

結果は

caw caw
Birds fly

これも前回と同じ結果。

Crow型ポインタにCrowコンストラク

    CCrow* b2;
    b2 = new CCrow();
    
    waiting(b2);
    throwing(b2);

結果は

caw caw
Birds fly

昨日の例ではfly()で"clow flies"となっていたのに、"Birds fly"になってしまった。当然だが、throwing関数の引数をCCrow型ポインタにしてやれば"clow flies"になる。

というわけで

BirdとCrowのようにクラスの中身(メンバ関数とかメンバ変数とか)が全く同じで、そのポインタを親クラスへのポインタと解釈する場合において、仮想関数が威力を発揮するらしい。 今回の例だと箱の中に入った鳥の種類を識別するために、鳴くまで待ってみて「カーカー」鳴いたからこれはカラスだな、みたいなことをやろうとすると仮想関数を使わざるを得ない…ということでとりあえず理解しておく。