Menu
Coddy logo textTech

メソッドのオーバーライド

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 51/104。

メソッドのオーバーライドは、派生クラスが基底クラスに既に存在するメソッドに対して、独自の実装を提供する場合に発生します。これにより、派生クラスは同じメソッドのシグネチャを維持しながら、継承された動作をカスタマイズすることができます。

メソッドをオーバーライドするには、派生クラスで、名前、戻り値の型、およびパラメータがまったく同じメソッドを定義するだけです。

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // 出力: Woof!

派生クラスのバージョンは、基底クラスのバージョンを隠します。しかし、スコープ解決演算子を使用して、基底クラスのメソッドにアクセスすることもできます:

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // 基底クラスのバージョンを呼び出す
        std::cout << "Woof!" << std::endl;
    }
};

この基本的なオーバーライドの形式には、重要な制限があります。基底クラスのポインタや参照を介して派生オブジェクトにアクセスすると、代わりに基底クラスのバージョンが呼び出されます。

Dog d;
Animal* ptr = &d;
ptr->speak();  // 出力: Some sound (not Woof!)

これは、コンパイラが実際のオブジェクトの型ではなく、ポインタの型に基づいて呼び出すメソッドを決定するために起こります。実際のオブジェクトに基づいて適切なメソッドが呼び出される真の実行時ポリモーフィズムを実現するには、仮想関数が必要になります。これについては次のレッスンで詳しく説明します。

challenge icon

チャレンジ

簡単

メソッドのオーバーライドを実際に体験できる通知システムを構築しましょう。ベースとなる Notifier クラスと、メッセージの配信方法をカスタマイズする2つの特化した通知クラスを作成します。

コードは以下の3つのファイルに分けて構成します:

  • Notifier.h: 以下の内容を持つベースの Notifier クラスを定義します:
    • protected な std::string recipient メンバ
    • 受信者名を受け取り、それを保存するコンストラクタ
    • Sending to <recipient>: <message> と出力する public な send(const std::string& message) メソッド
  • EmailNotifier.h: Notifier を公開継承(public inheritance)する EmailNotifier クラスを定義します:
    • 受信者を受け取り、それをベースクラスに渡すコンストラクタ
    • send() メソッドをオーバーライドし、まず Notifier::send() を使用してベースクラスのバージョンを呼び出し、次に [Email sent successfully] と出力するようにします
  • main.cpp: 2つの入力(それぞれ別の行)を読み込みます:
    1. 受信者名 (string)
    2. メッセージ内容 (string)

    main.cpp 内に直接、Notifier を公開継承する SMSNotifier クラスを定義します:

    • 受信者を受け取り、それをベースクラスに渡すコンストラクタ
    • send() メソッドをオーバーライドし、SMS to <recipient>: <message> と出力するようにします(ベースクラスの動作を呼び出さず、完全に置き換えます)

    同じ受信者を使用して、各通知タイプ(ベースの NotifierEmailNotifierSMSNotifier)のオブジェクトを1つずつ作成します。入力されたメッセージを使用してそれぞれの send() を呼び出し、各出力の間にセパレータ行を出力してください:

    --- Base Notifier ---
    <output>
    --- Email Notifier ---
    <output>
    --- SMS Notifier ---
    <output>

例えば、入力が AliceHello there! の場合:

--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!

EmailNotifier が親メソッドを最初に呼び出すことでベースの動作を拡張しているのに対し、SMSNotifier は独自の定義で完全に置き換えている点に注目してください。インクルードガードを忘れず、各ファイルに適切なヘッダーをインクルードしてください。

チートシート

メソッドのオーバーライドにより、派生クラスは基底クラスのメソッドと同じシグネチャ(名前、戻り値の型、およびパラメータ)を維持したまま、独自のメソッドの実装を提供することができます。

基本的なオーバーライドの構文:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // 出力: Woof!

派生クラスのメソッドは、基底クラスのバージョンを隠蔽します。派生クラスから基底クラスのメソッドを呼び出すには、スコープ解決演算子を使用します:

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // 基底クラスのバージョンを呼び出す
        std::cout << "Woof!" << std::endl;
    }
};

重要な制限事項: 基底クラスのポインタまたは参照を介して派生オブジェクトにアクセスする場合、基底クラスのバージョンが呼び出されます:

Dog d;
Animal* ptr = &d;
ptr->speak();  // 出力: Some sound (Woof! ではない)

これは、コンパイラが実際のオブジェクトの型ではなく、ポインタの型に基づいて呼び出すメソッドを決定するためです。真の実行時ポリモーフィズムを実現するには、仮想関数を使用してください。

自分で試してみよう

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

// TODO: ここで Notifier を公開継承する SMSNotifier クラスを定義します
// - recipient を基底クラスに渡すコンストラクタ
// - send() をオーバーライドして「SMS to <recipient>: <message>」と出力するようにします
//   (基底クラスの動作を完全に置き換え、親の send() は呼び出さないでください)

int main() {
    string recipient;
    string message;
    
    getline(cin, recipient);
    getline(cin, message);
    
    // TODO: recipient を指定して Notifier オブジェクトを作成します
    // "--- Base Notifier ---" を出力します
    // message を引数にして send() を呼び出します
    
    // TODO: recipient を指定して EmailNotifier オブジェクトを作成します
    // "--- Email Notifier ---" を出力します
    // message を引数にして send() を呼び出します
    
    // TODO: recipient を指定して SMSNotifier オブジェクトを作成します
    // "--- SMS Notifier ---" を出力します
    // message を引数にして send() を呼び出します
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン