C++ の小さな技術を紹介するシリーズ【小技C++ 全９回】＃４＜スケルトンクラス＞

#仮想関数
まずは、C++ が備える仮想関数の挙動について確認しよう。仮想関数とは、クラスが持つオーバーライド可能なメンバ関数のことだ。

##例
**Human** クラスは、仮想関数 **Serif** を持っている。

```cpp
class Human
{
public:
    virtual const char* Serif() const { return "I'm a Human." };
};
```

この **Serif** 関数はオーバーライドすることができる。

```cpp:継承とオーバーライド
//------------------------------------------------------------

class John : public Human
{
public:
    virtual const char* Serif() const override { return "I'm John." };
};

//------------------------------------------------------------

class Richie : public Human
{
public:
    virtual const char* Serif() const override { return "I'm Richie." };
};

//------------------------------------------------------------
```

このようにオーバーライドされた関数は、Base クラス（ここでは **Human** クラス）をインターフェイスとして、次のように呼び分けることができる。

```cpp:使用例
Human* pHuman = nullptr;

// John instance
pHuman = new John();
printf( "%s\n", pHuman->Serif() );
delete pHuman;

// Richie instance
pHuman = new Richie();
printf( "%s\n", pHuman->Serif() );
delete pHuman;
```
```cpp:実行結果
I'm John.
I'm Richie.
```
**John** 、**Richie** いずれのインスタンスも **Human** クラスのポインタを経由して仮想関数 **Serif** の呼び出しがなされる。しかし、実行されるのはオーバーライドされたそれぞれの **Serif** 関数であることがわかるだろう。このことを一般的にポリモーフィズム（多態性）と呼ぶ。

#スケルトンクラス
ここからは、前述のポリモーフィズムによる一種の『分岐処理』を使って、機能の拡張性が高いクラスの設計方法を紹介しよう。

##例題
犬を飼う人と、猫を飼う人の仕事内容をそれぞれ標準出力に印字したい。どちらも朝食と夕食を与える必要がある。加えて、犬は昼間に散歩を必要とすることがわかっている。

##解決方法
犬を飼う人と猫を飼う人の仕事には共通点がある。それは朝食と夕食を与えるという仕事のようだ。しかし、犬を飼う人には、散歩をするという仕事が特別にある。つまり、「散歩をする」という仕事は、犬を飼う人ならば出力する必要があり、猫を飼う人ならば出力する必要がないということになる。
###（方法１） **if** 文による分岐
まずはストレートに **if** 文を使って、分岐したい内容をそのままコードに書き起こしてみよう。

```cpp:方法１：定義
//---------------------------------------

enum class Animal
{
    Dog,
    Cat,
};

//---------------------------------------

class PetMaster
{
public:
    void print_work()
    {
        feed_morning(); // 朝食
        walk_daytime(); // 散歩
        feed_evening(); // 夕食
    }

public:
    PetMaster( const Animal& animal )
      : m_animal( animal ) {}

private:
    void feed_morning()
    {
        printf( "朝ごはんですよ.\n" );
    }

    void walk_daytime()
    {
        if( m_animal == Animal::Dog ) {
            printf( "散歩に行こう.\n" );
        }
    }

    void feed_evening()
    {
        printf( "晩ごはんですよ.\n" );
    }

private:

    Animal m_animal;
};

//---------------------------------------
```
**PetMaster** クラスは、 **enum** によって外部から与えられるペットの種類に従って、内部で仕事内容を変える。

```cpp:方法１：使用例
// DogMaster instance
printf( "--- DogMaster's work ---\n" );
PetMaster* pDogMaster = new PetMaster( Animal::Dog );
pDogMaster->print_work();
delete pDogMater;

// CatMaster instance
printf( "--- CatMaster's work ---\n" );
PetMaster* pCatMaster = new PetMaster( Animal::Cat );
pCatMaster->print_work();
delete pCatMaster;
```
```cpp:実行結果
--- DogMaster's work ---
朝ごはんですよ.
散歩に行こう.
晩ごはんですよ.
--- CatMaster's work ---
朝ごはんですよ.
晩ごはんですよ.
```
###（方法２）スケルトンクラスによる分岐
まずは、**PetMaster** に実装する必要のあるすべての"仕事"関数を仮想関数で宣言し、クラスを定義する。

>このとき、仮想関数の実装は必要がないため、純粋仮想関数で宣言する。ゆえに、このクラスを**スケルトンクラス**と呼ぶ。

```cpp:方法２：定義
//---------------------------------------

class PetMaster
{
public:
    void print_work()
    {
        feed_morning(); // 朝食
        walk_daytime(); // 散歩
        feed_evening(); // 夕食
    }

protected:
    virtual void feed_morning() = 0;
    virtual void walk_daytime() = 0;
    virtual void feed_evening() = 0;
};

//---------------------------------------
```
次に、この仮想関数をオーバーライドすることで実装を与える。この実装を与えるクラスこそが『犬を飼う人』『猫を飼う人』を表現するクラスとなる。

```cpp:方法２：オーバーライド
//---------------------------------------

class DogMaster : public PetMaster
{
protected:
    virtual void feed_morning() override { printf( "朝ごはんですよ.\n" ); }
    virtual void walk_daytime() override { printf( "散歩に行こう.\n" ); }
    virtual void feed_evening() override { printf( "晩ごはんですよ.\n" ); }
};

//---------------------------------------

class CatMaster : public PetMaster
{
protected:
    virtual void feed_morning() override { printf( "朝ごはんですよ.\n" ); }
    virtual void walk_daytime() override { /* do nothing */ }
    virtual void feed_evening() override { printf( "晩ごはんですよ.\n" ); }
};

//---------------------------------------
```
継承先のクラスである **DogMaster** 、**CatMaster** クラスでそれぞれの"仕事"関数に実装が与えられた。ここで確認しておきたいところは **CatMaster::walk_daytime** 関数だ。この仮想関数はオーバーライドしているものの関数内部は空っぽにしてある。つまり、仮想関数自体は実行されるが、何の影響もないまま呼び出し元に帰ることになる。

```cpp:方法２：使用例
PetMaster* pPetMaster = nullptr;

// DogMaster instance
printf( "--- DogMaster's work ---\n" );
pPetMaster = new DogMaster();
pPetMaster->print_work();
delete pPetMaster;

// CatMaster instance
printf( "--- CatMaster's work ---\n" );
pPetMaster = new CatMaster();
pPetMaster->print_work();
delete pPetMaster;
```
```cpp:実行結果
--- DogMaster's work ---
朝ごはんですよ.
散歩に行こう.
晩ごはんですよ.
--- CatMaster's work ---
朝ごはんですよ.
晩ごはんですよ.
```
###方法２の優位性
方法１に比べて、定義の依存度が明らかに低いことがわかるだろう。インスタンスの生成において、**enum** による分岐を行っているにも関わらず、**walk_daytime** 関数内部でも **if** 文による分岐命令を描く必要がある。スケルトンクラスを定義することで、生成するインスタンスのクラスを切り替えるだけで実装の選択を行うことができる。また、新たな **Animal** の種類が増えたり、"仕事"関数を増減したい場合に、変更箇所を小さく抑えることができることもスケルトンクラスの魅力の１つだろう。
#特徴を抽出するということ
プログラミングを行う上で最も重要で難しく、本質だと感じるのは、『物事の共通点や特徴を見極めること』。解決すべき問題を見つめ、その様相を読み解き、骨格を掘り出す。今回の紹介であれば、「何も必要としない /* do nothing */」こともまた特徴であるということだった。みなさんはコードを書くその一瞬前にどのようなことを感じ取り、意識的にまたは無意識的に何を見つけているのだろう。