Menu
CoddyTech

CRTP를 통한 Mixin

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

기이하게 반복되는 템플릿 패턴(CRTP)은 클래스가 템플릿 기본 클래스를 상속받으면서 자기 자신을 템플릿 인수로 전달하는 기법입니다. 이를 통해 컴파일 시간 다형성이 가능해지고, 기본 클래스가 가상 함수 없이 파생 클래스의 멤버에 접근할 수 있습니다.

기본 CRTP 구조는 다음과 같습니다:

template <typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class MyClass : public Base<MyClass> {
public:
    void implementation() {
        std::cout << "MyClass implementation\n";
    }
};

CRTP는 믹스인을 만드는 데 특히 강력합니다. 믹스인은 클래스에 "혼합해 넣을" 수 있는 재사용 가능한 기능입니다. 기존의 상속과 달리, 믹스인은 깊은 계층 구조를 만들지 않고 기능을 추가합니다:

template <typename Derived>
class Printable {
public:
    void print() const {
        const Derived& self = static_cast<const Derived&>(*this);
        std::cout << self.toString() << "\n";
    }
};

class Person : public Printable<Person> {
    std::string name;
public:
    Person(const std::string& n) : name(n) {}
    std::string toString() const { return "Person: " + name; }
};

// 사용법:
Person p("Alice");
p.print();  // 출력: Person: Alice

핵심적인 장점은 모든 메서드 호출이 컴파일 시점에 결정되어 가상 함수의 오버헤드가 없다는 것입니다. 여러 CRTP 믹스인을 결합해 기능을 구성할 수 있으므로, 컴파일 시점에 타입을 알고 있을 때 런타임 다형성을 대신할 유연한 대안이 됩니다.

challenge icon

챌린지

쉬움

가상 함수 오버헤드 없이 재사용 가능한 기능을 여러 클래스에 추가하는 CRTP 믹스인을 사용해 로깅 시스템을 만들어 봅시다. 어떤 클래스에든 "혼합할" 수 있는 믹스인 두 개를 만들 것입니다. 하나는 인스턴스 개수를 세고, 다른 하나는 문자열 표현을 생성합니다.

코드를 세 개의 파일에 나누어 작성합니다.

  • Mixins.h: 재사용 가능한 기능을 제공하는 CRTP 믹스인 템플릿 두 개를 정의합니다.

    파생 클래스의 인스턴스가 몇 개 존재하는지 추적하는 Countable 믹스인 템플릿을 만드세요. 생성자에서 증가하고 소멸자에서 감소하는 정적 카운터를 두어야 합니다. 현재 개수를 반환하는 정적 메서드 getCount()를 제공하세요.

    describe() 메서드를 제공하는 Describable 믹스인 템플릿을 만드세요. 이 메서드는 static_cast를 사용해 파생 클래스에 접근하고 해당 클래스의 getDescription() 메서드를 호출한 다음, 결과를 출력하고 줄바꿈해야 합니다.

    CRTP 믹스인은 컴파일 시점에 파생 클래스의 멤버에 접근하기 위해 static_cast<Derived*>(this)를 사용한다는 점을 기억하세요.

  • Entities.h: 두 믹스인을 모두 상속하는 엔터티 클래스 두 개를 정의합니다.

    Countable<Player>와 Describable<Player>를 모두 상속하는 Player 클래스를 만드세요. 이 클래스는 이름과 레벨을 저장하고, 다음 형식의 문자열을 반환하는 getDescription() 메서드를 제공해야 합니다. Player: [name] (Level [level])

    두 믹스인을 모두 상속하는 Enemy 클래스도 만드세요. 이 클래스는 유형과 체력을 저장하고, 다음 문자열을 반환하는 getDescription() 메서드를 제공해야 합니다. Enemy: [type] with [health] HP

    각 클래스의 정적 카운터를 초기화하는 것을 잊지 마세요.

  • main.cpp: 입력 네 개를 읽습니다(각각 별도의 줄에 입력).
    1. 플레이어 이름(문자열)
    2. 플레이어 레벨(정수)
    3. 적 유형(문자열)
    4. 적 체력(정수)

    주어진 값으로 Player와 Enemy를 생성합니다. 그런 다음 믹스인의 기능을 확인합니다.

    1. 정적 메서드 getCount()를 사용해 Player count: [count]를 출력합니다.
    2. Enemy count: [count]를 출력합니다.
    3. 플레이어에서 describe()를 호출합니다.
    4. 적에서 describe()를 호출합니다.
    5. 이름이 "Guest"이고 레벨이 1인 두 번째 플레이어를 생성합니다.
    6. 개수가 업데이트된 것을 보여 주도록 Player count: [count]를 다시 출력합니다.
    7. 두 번째 플레이어에서 describe()를 호출합니다.

예를 들어, 입력이 Hero, 10, Dragon, 500인 경우:

Player count: 1
Enemy count: 1
Player: Hero (Level 10)
Enemy: Dragon with 500 HP
Player count: 2
Player: Guest (Level 1)

이 챌린지에서는 CRTP 믹스인이 가상 함수를 사용하지 않고도 서로 관련 없는 클래스에 기능(개수 세기와 설명하기)을 추가하는 방법을 보여 줍니다. Player와 Enemy는 동일한 믹스인 템플릿을 상속하여 같은 기능을 얻지만, 서로 다른 타입으로 템플릿이 인스턴스화되므로 각자 별도의 인스턴스 카운터를 유지합니다.

직접 해보기

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

using namespace std;

int main() {
    // 입력값 읽기
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: 주어진 이름과 레벨로 Player 생성
    
    // TODO: 주어진 유형과 체력으로 Enemy 생성
    
    // TODO: Player::getCount()를 사용해 "Player count: [count]" 출력
    
    // TODO: Enemy::getCount()를 사용해 "Enemy count: [count]" 출력
    
    // TODO: 플레이어의 describe() 호출
    
    // TODO: 적의 describe() 호출
    
    // TODO: 이름이 "Guest"이고 레벨이 1인 두 번째 플레이어 생성
    
    // TODO: "Player count: [count]" 다시 출력
    
    // TODO: 두 번째 플레이어의 describe() 호출
    
    return 0;
}
quiz icon실력 점검

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

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

직접 연습해 보세요: 온라인 C++ 컴파일러