Menu
Coddy logo textTech

純粋仮想関数

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

純粋仮想関数とは、基底クラスに実装を持たない仮想関数です。関数宣言に = 0 を割り当てることで宣言します。これは、派生クラスが独自の実装を必ず提供しなければならないことをコンパイラに伝えます。

class Shape {
public:
    virtual double area() = 0;  // 純粋仮想関数
    virtual ~Shape() = default;
};

デフォルトの動作を提供する通常の仮想関数とは異なり、純粋仮想関数は契約を定義します。具象派生クラスは、この関数を実装しなければインスタンス化できません。基底クラスは、単に何を行う必要があるかを宣言するだけであり、どのように行うかは定義しません。

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
};

CircleRectangle はどちらも Shape 内で純粋仮想関数となっているため、area() を実装する必要があります。派生クラスがすべての純粋仮想関数の実装に失敗すると、そのクラスも抽象クラスとなり、インスタンス化できません。

純粋仮想関数は、基底クラスが意味のあるデフォルト実装を提供できない場合に不可欠です。すべての図形には area がありますが、特定の図形型を知らずに「一般的な図形の area」を calculate する適切な方法はありません。

challenge icon

チャレンジ

簡単

異なる支払い方法全体で純粋仮想関数がどのように契約を強制するかを示す、支払い処理システムを構築しましょう。すべての支払いプロセッサーが実行すべき内容を定義する抽象基底 class を作成し、その契約を満たす具象的な支払いタイプを実装します。

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

  • PaymentProcessor.h: すべての支払い方法の設計図となる抽象 PaymentProcessor class を定義します。この class には次の要素を含めます。
    • protected な std::string accountId メンバー
    • account ID を初期化する constructor
    • 純粋仮想 processPayment(double amount) メソッド:すべての支払いタイプがそれぞれ異なる方法で実装する必要があります
    • std::string を返す純粋仮想 getProcessorName() メソッド
    • virtual destructor
  • PaymentMethods.h: PaymentProcessor を継承する2つの具象的な支払いプロセッサーを実装します。

    CreditCardProcessor:

    • private な double feePercentage メンバー(取引手数料率)
    • account ID と手数料率を受け取る constructor
    • processPayment() を実装して手数料(amount * feePercentage / 100)を計算し、次を出力します。
      Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)
    • getProcessorName() を実装して "CreditCard" を返します

    BankTransferProcessor:

    • private な std::string bankName メンバー
    • account ID と銀行名を受け取る constructor
    • processPayment() を実装して次を出力します。
      Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>
    • getProcessorName() を実装して "BankTransfer" を返します
  • main.cpp: 4つの入力を読み取ります(それぞれ別の行に入力します)。
    1. Credit card の account ID
    2. Credit card の手数料率(double)
    3. Bank の account ID
    4. Bank の名前

    両方の支払いプロセッサーを作成し、PaymentProcessor* ポインターの array に格納します。各プロセッサーを通して 100.0 の支払いを処理し、各取引の前にプロセッサー名を表示します。

    Processing with <processorName>:
    <processPayment output>

    プロセッサーの間に空行を出力します。処理が終わったら、dynamically allocated されたオブジェクトを Clean up します。

たとえば、入力が CC-45212.5BA-7890National Bank の場合:

Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)

Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100

PaymentProcessor には純粋仮想関数があるため、直接インスタンス化することはできません。作成できるのは、契約を満たす具象的な implementation のみです。これにより、すべての支払い方法がそれぞれ固有の処理ロジックを提供することが保証されます。

自分で試してみよう

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // 入力を読み取る
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: PaymentProcessor* ポインタの配列を2要素で作成する
    
    // TODO: CreditCardProcessor と BankTransferProcessor のオブジェクトを作成する
    // そしてそれらを配列に格納する
    
    // TODO: 配列をループして各プロセッサについて:
    // 1. Print "Processing with <processorName>:"
    // 2. amount 100.0 で processPayment を呼び出す
    // 3. プロセッサ間に空行を出力する(最後の後には出力しない)
    
    // TODO: 動的に割り当てられたオブジェクトをクリーンアップする
    
    return 0;
}
quiz icon腕試し

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

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

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