仮想関数の復習
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 58/104。
コンパイル時ポリモーフィズムと実行時ポリモーフィズムの違いを理解したところで、仮想関数と、それらをより安全に使用できるようにする override キーワードについて、さらに詳しく見ていきましょう。
base class で function を virtual としてマークすると、派生クラスで独自の実装を提供できます。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 を使用してください。これにより意図が文書化され、実際に既存の仮想関数をオーバーライドしていることをコンパイラで検証できます。
チャレンジ
簡単override と final キーワードを仮想関数とともに使用し、その力を示す通知システムを構築しましょう。通知ハンドラーの階層を作成し、一部のメソッドはさらにカスタマイズでき、その他のメソッドは変更を防ぐためにロックダウンされるようにします。
コードを次の3つのファイルに分けて整理します。
Notifier.h:あらゆる通知送信者を表す基底Notifierクラスを定義します。- protected な
std::string recipientメンバー - 受信者名を受け取り、保存するコンストラクター
Notifying <recipient>: <message>と出力する仮想send(const std::string& message)メソッド- 文字列
"Generic"を返す仮想getType()メソッド - 仮想デストラクター
- protected な
EmailNotifier.h:Notifierを継承するEmailNotifierクラスを定義します。- private な
std::string domainメンバー - 受信者とドメインを受け取り、受信者を基底クラスに渡すコンストラクター
Emailing <recipient>@<domain>: <message>と出力するようにsend()をオーバーライドgetType()をオーバーライドしてfinalと指定します。"Email"を返す必要があります
次に、
EmailNotifierクラスを継承するUrgentEmailNotifierクラスを定義します。- 受信者とドメインを受け取り、両方を
EmailNotifierに渡すコンストラクター [URGENT] Emailing <recipient>@<domain>: <message>と出力するようにsend()をオーバーライド- 注:
EmailNotifierでfinalと指定されているため、ここでgetType()をオーバーライドすることはできません
- private な
main.cpp:3つの入力を読み取ります(それぞれ別の行に入力します)。- 受信者名
- メールドメイン
- メッセージテキスト
3つの通知オブジェクトを動的に作成します:基底
Notifier、EmailNotifier、UrgentEmailNotifierです。すべて同じ受信者(該当する場合はドメインも)を使用します。これらをNotifier*ポインターの配列に格納します。配列をループし、各通知について
getType()を使ってその型を出力し、その後メッセージを指定してsend()を呼び出します。各項目を次の形式にします。Type: <type> <send output>各通知の間に空行を出力します。処理が終わったら、動的に確保したオブジェクトを解放します。
たとえば、入力が Alice、company.com、Meeting 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 3pmUrgentEmailNotifier は send() をオーバーライドしてメッセージ形式をカスタマイズできますが、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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ