Menu
Coddy logo textTech

순수 가상 함수

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 59번째.

순수 가상 함수는 기본 클래스에 구현이 없는 가상 함수입니다. 이는 함수 선언에 = 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()가 순수 가상 함수이므로 이를 구현해야 합니다. 파생 클래스가 모든 순수 가상 함수를 구현하지 않으면, 해당 클래스도 추상 클래스가 되어 인스턴스화할 수 없습니다.

순수 가상 함수는 기본 클래스가 의미 있는 기본 구현을 제공할 수 없을 때 필수적입니다. 모든 도형은 면적을 가지지만, 구체적인 도형 유형을 알지 못하면 "일반적인 도형의 면적"을 계산할 합리적인 방법이 없습니다.

challenge icon

챌린지

쉬움

순수 가상 함수가 어떻게 서로 다른 결제 수단 간의 계약을 강제하는지 보여주는 결제 처리 시스템을 구축해 보겠습니다. 모든 결제 프로세서가 수행해야 할 작업을 정의하는 추상 기본 클래스를 만든 다음, 해당 계약을 충족하는 구체적인 결제 유형을 구현합니다.

코드는 세 개의 파일로 구성됩니다:

  • PaymentProcessor.h: 모든 결제 수단의 청사진 역할을 하는 추상 PaymentProcessor 클래스를 정의합니다. 이 클래스는 다음을 포함해야 합니다:
    • 보호된(protected) std::string accountId 멤버
    • 계정 ID를 초기화하는 생성자
    • 순수 가상 processPayment(double amount) 메서드 — 모든 결제 유형은 이를 다르게 구현해야 합니다.
    • std::string을 반환하는 순수 가상 getProcessorName() 메서드
    • 가상 소멸자
  • PaymentMethods.h: PaymentProcessor를 상속받는 두 개의 구체적인 결제 프로세서를 구현합니다:

    CreditCardProcessor:

    • 비공개(private) double feePercentage 멤버 (거래 수수료율)
    • 계정 ID와 수수료율을 받는 생성자
    • 수수료(amount * feePercentage / 100)를 계산한 후 다음을 출력하도록 processPayment()를 구현합니다:
      Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;fee>)
    • "CreditCard"를 반환하도록 getProcessorName()을 구현합니다.

    BankTransferProcessor:

    • 비공개(private) std::string bankName 멤버
    • 계정 ID와 은행 이름을 받는 생성자
    • 다음을 출력하도록 processPayment()를 구현합니다:
      Bank Transfer [<accountId>] via <bankName>: Transferred Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount>
    • "BankTransfer"를 반환하도록 getProcessorName()을 구현합니다.
  • main.cpp: 네 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨):
    1. 신용카드 계정 ID
    2. 신용카드 수수료율 (double)
    3. 은행 계좌 ID
    4. 은행 이름

    두 결제 프로세서를 모두 생성하고 PaymentProcessor* 포인터 배열에 저장합니다. 각 프로세서를 통해 100.0의 결제를 처리하며, 각 거래 전에 프로세서 이름을 표시합니다:

    Processing with <processorName>:
    <processPayment output>

    프로세서 사이에 빈 줄을 출력합니다. 작업이 끝나면 동적으로 할당된 객체들을 정리합니다.

예를 들어, 입력값이 CC-4521, 2.5, BA-7890, National 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는 순수 가상 함수를 가지고 있기 때문에 직접 인스턴스화할 수 없습니다. 계약을 충족하는 구체적인 구현체만 생성할 수 있습니다. 이를 통해 모든 결제 수단이 자신만의 구체적인 처리 로직을 제공하도록 보장합니다.

치트 시트

순수 가상 함수(pure virtual function)는 가상 함수에 = 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;
    }
};

파생 클래스가 모든 순수 가상 함수를 구현하지 않으면, 해당 클래스 또한 추상 클래스가 되어 인스턴스화할 수 없습니다.

기본 클래스가 의미 있는 기본 구현을 제공할 수 없을 때 순수 가상 함수를 사용하세요.

직접 해보기

#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: 2개의 요소를 가진 PaymentProcessor* 포인터 배열 생성
    
    // TODO: CreditCardProcessor 및 BankTransferProcessor 객체 생성
    // 그리고 그것들을 배열에 저장
    
    // TODO: 배열을 순회하며 각 프로세서에 대해 다음을 수행:
    // 1. "Processing with <processorName>:" 출력
    // 2. 금액 100.0으로 processPayment 호출
    // 3. 프로세서 사이에 빈 줄 출력 (마지막 프로세서 뒤에는 제외)
    
    // TODO: 동적으로 할당된 객체 정리
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨