抽象クラス
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 60/104。
抽象クラスとは、少なくとも1つの純粋仮想関数を含むクラスのことです。不完全なインターフェースを持っているため、抽象クラスのオブジェクトを直接作成することはできません。それは単に継承されるためだけに存在します。
class Shape {
public:
virtual double area() = 0; // 純粋仮想関数 - Shapeを抽象クラスにします
virtual double perimeter() = 0; // 別の純粋仮想関数
void printInfo() { // 通常のメソッド - 実装を持ちます
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // エラー: 抽象クラスをインスタンス化できません抽象クラスには、純粋仮想関数(実装なし)、通常の仮想関数(デフォルトの実装あり)、および非仮想関数を混在させることができます。これにより、共通の動作を定義しつつ、派生クラスに特定の機能を実装させることが可能になります。
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: Circleはすべての純粋仮想関数を実装しています
c.printInfo(); // 継承されたprintInfo()を使用し、Circleのarea()を呼び出します派生クラスが具象クラス(インスタンス化可能)になるためには、すべての純粋仮想関数を実装しなければなりません。もし1つでも未実装のままにすると、そのクラス自体も抽象クラスのままとなります。この仕組みにより、階層内のすべての具象クラスが、必要な機能を確実に提供するようになります。
チャレンジ
簡単抽象クラスが異なるメディアタイプのコントラクトをどのように定義するかを実証するメディアプレーヤーシステムを構築しましょう。すべてのメディアプレーヤーが実行すべきことを確立する抽象基底クラスを作成し、次にオーディオおよびビデオコンテンツ用の具体的なプレーヤーを実装します。
コードを3つのファイルに分けて構成します:
MediaPlayer.h: すべてのメディアタイプの設計図となる抽象クラスMediaPlayerを定義します。抽象クラスには以下を含める必要があります:- protected な
std::string titleメンバ - protected な
int durationメンバ(秒単位) - 両方の値を初期化するコンストラクタ
- 純粋仮想関数
play()メソッド — 各メディアタイプで再生方法が異なります std::stringを返す純粋仮想関数getMediaType()メソッド<title> (<duration>s) - Type: <mediaType>と出力する通常の(非仮想)showInfo()メソッド — このメソッドはgetMediaType()を呼び出してタイプを取得する必要があります- 仮想デストラクタ
- protected な
Players.h:MediaPlayerを継承する2つの具体的なメディアプレーヤーを実装します:AudioPlayer:- private な
std::string artistメンバ - タイトル、再生時間、アーティストを受け取るコンストラクタ
Playing audio: <title> by <artist>と出力するようにplay()を実装します"Audio"を返すようにgetMediaType()を実装します
VideoPlayer:- private な
std::string resolutionメンバ - タイトル、再生時間、解像度を受け取るコンストラクタ
Playing video: <title> in <resolution>と出力するようにplay()を実装します"Video"を返すようにgetMediaType()を実装します
- private な
main.cpp: 4つの入力(それぞれ別の行)を読み取ります:- オーディオトラックのタイトル
- オーディオの再生時間(整数)
- ビデオのタイトル
- ビデオの再生時間(整数)
アーティストを
"Unknown Artist"としたAudioPlayerと、解像度を"1080p"としたVideoPlayerを作成します。両方をMediaPlayer*ポインタの配列に格納します。配列をループし、各プレーヤーに対して最初に
showInfo()を呼び出し、次にplay()を呼び出します。各メディア項目の間に空行を出力してください。終了時には、動的に割り当てられたオブジェクトをクリーンアップしてください。
例えば、入力が Summer Vibes、180、Nature Documentary、3600 の場合:
Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist
Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080pshowInfo() メソッドが抽象基底クラスで一度だけ定義されているにもかかわらず、純粋仮想関数 getMediaType() を呼び出していることに注目してください。これは、抽象クラスが具体的な実装と、派生クラスが提供しなければならないメソッドをどのように混在させることができるかを示しています。すべてのオーバーライドされたメソッドには override キーワードを使用することを忘れないでください。
チートシート
抽象クラスは、少なくとも1つの純粋仮想関数を含み、直接インスタンス化することはできません。これは、継承されること、および派生クラスのインターフェースを定義することを目的として存在します。
抽象クラスの定義
純粋仮想関数を宣言するには、= 0を使用します:
class Shape {
public:
virtual double area() = 0; // 純粋仮想関数
virtual double perimeter() = 0; // Shapeを抽象クラスにする
void printInfo() { // 実装を持つ通常のメソッド
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default; // 仮想デストラクタ
};抽象クラスには以下を含めることができます:
- 純粋仮想関数(実装なし)
- 通常の仮想関数(デフォルトの実装あり)
- 非仮想関数
具象クラスの実装
派生クラスがインスタンス化可能になるには、すべての純粋仮想関数を実装する必要があります:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: すべての純粋仮想関数が実装されている
c.printInfo(); // 継承されたメソッドがCircleのarea()を呼び出す派生クラスがすべての純粋仮想関数を実装していない場合、そのクラスは抽象クラスのままであり、インスタンス化することはできません。
自分で試してみよう
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// 入力を読み込む
std::string audioTitle;
int audioDuration;
std::string videoTitle;
int videoDuration;
std::getline(std::cin, audioTitle);
std::cin >> audioDuration;
std::cin.ignore();
std::getline(std::cin, videoTitle);
std::cin >> videoDuration;
// TODO: アーティスト名を "Unknown Artist" として AudioPlayer を作成する
// TODO: 解像度を "1080p" として VideoPlayer を作成する
// TODO: 両方を MediaPlayer* ポインタの配列に格納する
// TODO: 配列をループで回し、各プレイヤーに対して以下を行う:
// 1. showInfo() を呼び出す
// 2. play() を呼び出す
// 3. 各メディア項目の間に空行を出力する
// TODO: 動的に割り当てられたオブジェクトをクリーンアップする
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス