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

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

クラスに関して③:仮想関数と完全仮想関数

たまに関数の頭にvirtualがついているのを見かけるので、それの内容。

仮想関数

cpp-lang.sevendays-study.com

を参考にちょっと変更したコードを作成。

bird.h

#ifndef __BIRD_H__
#define __BIRD_H__

#include <iostream>
#include <string>

using namespace std;

class CBird{
    public:
        virtual void sing(){ cout << "Birds sing" << endl; }
        void fly(){ cout << "Birds fly" << endl; }
};

#endif // __BIRD_H__

crow.h

#ifndef __CROW_H__
#define __CROW_H__

#include "bird.h"

class CCrow : public CBird{
    public:
        void sing(){ cout <<  "caw caw" <<endl; }
        void fly(){ cout << "Crow flies" << endl; }
};

#endif // __CROW_H__

main.cpp

#include <iostream>
#include <string>
#include "bird.h"
#include "crow.h"

using namespace std;

int main(){
    CBird* b0;
    b0 = new CBird();

    b0->sing();
    b0->fly();

    CBird* b1;
    b1 = new CCrow();

    b1->sing();
    b1->fly();

    CCrow* b2;
    b2 = new CCrow();
    
    b2->sing();
    b2->fly();

    return 0;
}

birdクラスでsing()とfly()という2種類のメンバ関数を宣言。うちsingはvirtualをつけた仮想関数。 crowクラスはbirdクラスを継承してsing(),fly()の中身を変えている。 mainでは

  1. birdクラスへのポインタとしてb0を宣言して、CBird()コンストラクタを使用
  2. birdクラスへのポインタとしてb1を宣言して、CCrow()コンストラクタを使用
  3. crowクラスへのポインタとしてb1を宣言して、CCrow()コンストラクタを使用

結果は、

$ ./a.out
Birds sing
Birds fly
caw caw
Birds fly
caw caw
Crow flies
  1. birdクラスのsing(),fly()が呼ばれる(自明)
  2. crowクラスのsing()とbirdクラスのfly()が呼ばれる
  3. crowクラスのsing()とfly()が呼ばれる(自明)

ということで、2番目のケースにおいて、virtualをつけて仮想関数としたsingは子クラスのcrowクラスのものが呼ばれ、普通の関数のfly()はbirdクラスのものが呼ばれている。 あえて親クラスのポインタを子クラスのコンストラクタでインスタンスする理由がよくわからないが…

完全仮想関数

bird.hを以下のように変更。

//        virtual void sing(){ cout << "Birds sing" << endl; }
        virtual void sing() = 0;
        void fly(){ cout << "Birds fly" << endl; }

これによって、sing()は完全仮想関数というものになる。 コンパイルすると、

$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:10:20: error: cannot allocate an object of abstract type ‘CBird’
     b0 = new CBird();
                    ^
In file included from main.cpp:3:0:
bird.h:9:7: note:   because the following virtual functions are pure within ‘CBird’:
 class CBird{
       ^
bird.h:12:22: note:     virtual void CBird::sing()
         virtual void sing() = 0;
                      ^

のようにbirdクラスのコンストラクタを呼び出すところで怒られる。 完全仮想関数を1つでもメンバとして持つクラスは抽象クラスといって、抽象クラスのインスタンスはできない。