Menu
Coddy logo textTech

抽象クラス

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つでも未実装のままにすると、そのクラス自体も抽象クラスのままとなります。この仕組みにより、階層内のすべての具象クラスが、必要な機能を確実に提供するようになります。

challenge icon

チャレンジ

簡単

抽象クラスが異なるメディアタイプのコントラクトをどのように定義するかを実証するメディアプレーヤーシステムを構築しましょう。すべてのメディアプレーヤーが実行すべきことを確立する抽象基底クラスを作成し、次にオーディオおよびビデオコンテンツ用の具体的なプレーヤーを実装します。

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

  • MediaPlayer.h: すべてのメディアタイプの設計図となる抽象クラス MediaPlayer を定義します。抽象クラスには以下を含める必要があります:
    • protected な std::string title メンバ
    • protected な int duration メンバ(秒単位)
    • 両方の値を初期化するコンストラクタ
    • 純粋仮想関数 play() メソッド — 各メディアタイプで再生方法が異なります
    • std::string を返す純粋仮想関数 getMediaType() メソッド
    • <title> (<duration>s) - Type: <mediaType> と出力する通常の(非仮想) showInfo() メソッド — このメソッドは getMediaType() を呼び出してタイプを取得する必要があります
    • 仮想デストラクタ
  • 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() を実装します
  • main.cpp: 4つの入力(それぞれ別の行)を読み取ります:
    1. オーディオトラックのタイトル
    2. オーディオの再生時間(整数)
    3. ビデオのタイトル
    4. ビデオの再生時間(整数)

    アーティストを "Unknown Artist" とした AudioPlayer と、解像度を "1080p" とした VideoPlayer を作成します。両方を MediaPlayer* ポインタの配列に格納します。

    配列をループし、各プレーヤーに対して最初に showInfo() を呼び出し、次に play() を呼び出します。各メディア項目の間に空行を出力してください。終了時には、動的に割り当てられたオブジェクトをクリーンアップしてください。

例えば、入力が Summer Vibes180Nature Documentary3600 の場合:

Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist

Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080p

showInfo() メソッドが抽象基底クラスで一度だけ定義されているにもかかわらず、純粋仮想関数 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;
}
quiz icon腕試し

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

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