Menu
Coddy logo textTech

仮想関数の復習

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

コンパイル時ポリモーフィズムと実行時ポリモーフィズムの違いを理解したところで、仮想関数と、それらをより安全に使用できるようにする override キーワードについて、さらに詳しく見ていきましょう。

base class で functionvirtual としてマークすると、派生クラスで独自の実装を提供できます。override 指定子は、virtual function をオーバーライドする意図があることを Compiler に明示的に伝えます。

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

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

override を使用することは、コンパイル時にエラーを検出できるため重要です。誤って function 名のスペルを間違えたり、誤ったパラメーターを使用したりすると、コンパイラーは警告を出します。そのため、気付かないうちに新しい function が作成されることはありません。

class Cat : public Animal {
public:
    void speek() override {  // コンパイラエラー: オーバーライドする関数がありません
        std::cout << "Meow!" << std::endl;
    }
};

final指定子は、それ以降のオーバーライドを防ぎます。派生クラスが特定のfunctionをオーバーライドする最後のクラスであるべき場合に使用します。

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // エラー: final 関数をオーバーライドできません
};

仮想関数をオーバーライドするときは、必ず override を使用してください。これにより意図が文書化され、実際に既存の仮想関数をオーバーライドしていることをコンパイラで検証できます。

challenge icon

チャレンジ

簡単

overridefinal キーワードを仮想関数とともに使用し、その力を示す通知システムを構築しましょう。通知ハンドラーの階層を作成し、一部のメソッドはさらにカスタマイズでき、その他のメソッドは変更を防ぐためにロックダウンされるようにします。

コードを次の3つのファイルに分けて整理します。

  • Notifier.h:あらゆる通知送信者を表す基底 Notifier クラスを定義します。
    • protected な std::string recipient メンバー
    • 受信者名を受け取り、保存するコンストラクター
    • Notifying <recipient>: <message> と出力する仮想 send(const std::string& message) メソッド
    • 文字列 "Generic" を返す仮想 getType() メソッド
    • 仮想デストラクター
  • EmailNotifier.hNotifier を継承する EmailNotifier クラスを定義します。
    • private な std::string domain メンバー
    • 受信者とドメインを受け取り、受信者を基底クラスに渡すコンストラクター
    • Emailing <recipient>@<domain>: <message> と出力するように send() をオーバーライド
    • getType() をオーバーライドして final と指定します。"Email" を返す必要があります

    次に、EmailNotifier クラスを継承する UrgentEmailNotifier クラスを定義します。

    • 受信者とドメインを受け取り、両方を EmailNotifier に渡すコンストラクター
    • [URGENT] Emailing <recipient>@<domain>: <message> と出力するように send() をオーバーライド
    • 注:EmailNotifierfinal と指定されているため、ここで getType() をオーバーライドすることはできません
  • main.cpp:3つの入力を読み取ります(それぞれ別の行に入力します)。
    1. 受信者名
    2. メールドメイン
    3. メッセージテキスト

    3つの通知オブジェクトを動的に作成します:基底 NotifierEmailNotifierUrgentEmailNotifier です。すべて同じ受信者(該当する場合はドメインも)を使用します。これらを Notifier* ポインターの配列に格納します。

    配列をループし、各通知について getType() を使ってその型を出力し、その後メッセージを指定して send() を呼び出します。各項目を次の形式にします。

    Type: <type>
    <send output>

    各通知の間に空行を出力します。処理が終わったら、動的に確保したオブジェクトを解放します。

たとえば、入力が Alicecompany.comMeeting at 3pm の場合:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

UrgentEmailNotifiersend() をオーバーライドしてメッセージ形式をカスタマイズできますが、getType()final と指定されているため、EmailNotifier から "Email" 型を継承していることに注目してください。コンパイル時にシグネチャの不一致を検出できるよう、オーバーライドするすべてのメソッドで override キーワードを使用してください。

自分で試してみよう

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

using namespace std;

int main() {
    // 入力を読み取る
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: Notifier* ポインタの配列を要素数3で作成する
    
    // TODO: 動的に作成する:
    // - recipient を使った基底の Notifier
    // - recipient と domain を使った EmailNotifier
    // - recipient と domain を使った UrgentEmailNotifier
    
    // TODO: 配列をループし、各 notifier について:
    // - "Type: " の後に getType() の結果を出力する
    // - message を渡して send() を呼び出す
    // - notifier の間に空行を出力する(最後の後には出力しない)
    
    // TODO: 動的に割り当てられたオブジェクトを解放する
    
    return 0;
}
quiz icon腕試し

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

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

自分で練習してみよう: C++オンラインコンパイラ