ショートカット
ファシリテーター × あり方
コーディングの向こう側
Hello, ANOTHER world!
オブジェクト指向のはなし
プログラミングのはなし
C言語実力診断クイズ
eSkillBooks
オブジェクト指向のはなし

継承とクラス階層の記述

導入

クラス宣言でスーパークラスを指定することによって継承を実現します。スーパークラスのメソッドは、継承されたクラスのメソッドと同じように使うことができます。

用語
このトピックでは次の用語を覚えます:
解説

例として、「電話(Phone)」クラスを考えてみましょう。あとからこれを継承した「留守番電話(Answerphone)」クラスを作成することにします。

電話クラスには次の2つのメソッドがあるものとします。

  • 電話をかけるときに呼ぶメソッド(MakeCall):
    相手を表す ID(便宜上 int 型にしてあります)を引数にとり、通話に成功したかどうかのステータスを返す
  • 電話を受けるべき時に呼ばれるメソッド(OnReceiveCall):
    相手を表す ID(便宜上 int 型にしてあります)を引数にとり、通話を受け付けたかどうかのステータスを返す
SAMPLE CODE
Phone クラス宣言ファイル(ファイル名:"Phone.h")

#ifndef INCLUDED_Phone
#define INCLUDED_Phone

class Phone {
public:
    Phone();
    virtual ~Phone();
    
// 電話の基本的機能に関するメソッド
public:
    // 電話をかける
    bool MakeCall( int nToSomebody );
    
    // 電話を受信する(イベント処理)
    virtual bool OnReceiveCall( int nFromSomebody );
};

#endif // #ifndef INCLUDED_Phone

宣言中に virtual というキーワードがありますが、これはポリモーフィズムのためのものです(別のトピックで詳しく説明します)。

継承の宣言

それでは「電話」クラスを継承して「留守番電話」クラスを作りましょう。といっても、実際には非常に単純な記述だけで済んでしまいます。宣言中に継承するクラスの名称(ここでは Phone)を書くだけです。

SAMPLE CODE
Answerphone クラス宣言ファイル(ファイル名:"Answerphone.h")

#ifndef INCLUDED_Answerphone
#define INCLUDED_Answerphone

#include "Phone.h"

class Answerphone : public Phone {
public:
    Answerphone();
    virtual ~Answerphone();
    
// 留守番電話で変更される機能に関するメソッド
public:
    // 電話を受信する(イベント処理)
    virtual bool OnReceiveCall( int nFromSomebody );
    
// 留守番電話に特有の機能に関するメソッド
public:
    // 録音された音声を再生する
    void PlayVoice();
};

#endif // #ifndef INCLUDED_Answerphone

このように public キーワードによって継承元を記述するだけで、スーパークラスである「電話」のメソッドがすべて「留守番電話」でも利用できるようになります。これは、「留守番電話は電話の一種である」という Is-A 関係を表現しています。

継承によってすべてのメソッドを引き継いだ上で、「留守番電話」クラスに特有の機能を追加します。また変更が必要なメソッドをオーバーライドします。オーバーライドとは、スーパークラスに備わっているものと同じ名前のメソッドをサブクラス側で上書き定義することです。これによって、サブクラスを使用しているときにはスーパークラスのメソッドがキャンセルされ、サブクラス側の定義が採用されるようになります。ここでは OnReceiveCall がオーバーライドされたメソッドです。オーバーロードの特例であると考えれば良いでしょう。

余談ですが、private や protected というキーワードによる継承も存在します。しかしこれらを使う必要はほとんどありません。もし、これらの継承がふさわしいと思える場面に出会ったら、コンポジションを検討してください。本来コンポジションを使うべきところでこれらの継承を誤って使う場合が多いのです。

また、public キーワードを書き忘れないようにしてください。これは、デフォルトで private として継承されるルールになっているためです。本当は public がデフォルトになっていると良いのですが、そうなってはいません。

スーパークラスのメソッドの流用

継承元の機能を継承先の実装の時に使えると便利な場合があります。「留守番電話」クラスの実装を行う場合を考えてみましょう。以下はおおざっぱな実装例です。

SAMPLE CODE
Answerphone クラス定義ファイル(ファイル名:"Answerphone.cpp")

#include "Answerphone.h"

// コンストラクタ
Answerphone::Answerphone()
{
}

// デストラクタ
Answerphone::~Answerphone()
{
}

// 電話を受信する(イベント処理)
bool Answerphone::OnReceiveCall( int nFromSomebody )
{
    bool bReceived = false; // 電話を受けたかどうか
    
    if ( /* 留守番モードか */ ) {
        
        while ( /* しばらくの間 */ ) {
            if ( /* 人が電話にでたか */ ) {
                // 通常の通話処理
                ;
                
                bReceived = true;
                break;
            }
        }
        
        // 時間切れの場合は録音開始
        if ( not bReceived ) {
            ;
        }
    }
    else {
        // 通常の受信処理
        bReceived = Phone::OnReceiveCall( nFromSomebody );
    }
    
    return bReceived;
}

// 録音された音声を再生する
void Answerphone::PlayVoice()
{
    // 再生
    ;
}

この例では、OnReceiveCall メソッドの中で Phone クラスの OnReceiveCall メソッドを利用しています。しかしこれら両クラスのメソッドは名前が同じなので、スコープを解決しないと使えません。そのための記述が Phone:: です。これで、たとえ同じ名前のメソッドでもどのクラスのものか明示的に指定して利用することができます。

なお、名前が重なっていない場合は Phone:: の指定は不要です(もちろん指定しても良いです)。

さて、次のトピックでは、今まで後回しにしてきた virtual キーワードの意味を説明します。

もっと!
もっと詳しく知りたい人は次の用語について調べると良いでしょう:
  • protected によってアクセス制限されたメソッド
  • コンストラクタ・デストラクタの連鎖

(「オブジェクト指向のはなし」は1999年2月から2000年4月にかけて作成されたコンテンツです。)