C++ Mixin | "믹스인" 가이드

C++ Mixin | "믹스인" 가이드

이 글의 핵심

C++ Mixin에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

Mixin이란?

기능 조합 패턴

#include <iostream>

// Mixin 클래스들
template<typename Base>
class Printable : public Base {
public:
    void print() const {
        std::cout << "Printing...\n";
    }
};

template<typename Base>
class Serializable : public Base {
public:
    void serialize() const {
        std::cout << "Serializing...\n";
    }
};

// 베이스
class Widget {
public:
    void doWork() {
        std::cout << "Working...\n";
    }
};

// 조합
using MyWidget = Serializable<Printable<Widget>>;

int main() {
    MyWidget w;
    w.doWork();
    w.print();
    w.serialize();
}

기본 패턴

// Mixin 템플릿
template<typename Base>
class Mixin : public Base {
public:
    void newFeature() { /* ... */ }
};

// 사용
class MyClass : public Mixin<SomeBase> {};

실전 예시

예시 1: 다중 Mixin

#include <iostream>
#include <string>

template<typename Base>
class Loggable : public Base {
public:
    void log(const std::string& msg) const {
        std::cout << "[LOG] " << msg << '\n';
    }
};

template<typename Base>
class Timestamped : public Base {
public:
    void setTimestamp(long ts) {
        timestamp = ts;
    }
    
    long getTimestamp() const {
        return timestamp;
    }
    
private:
    long timestamp = 0;
};

template<typename Base>
class Versioned : public Base {
public:
    void setVersion(int v) {
        version = v;
    }
    
    int getVersion() const {
        return version;
    }
    
private:
    int version = 1;
};

// 베이스
class Document {
public:
    void setContent(const std::string& c) {
        content = c;
    }
    
    std::string getContent() const {
        return content;
    }
    
private:
    std::string content;
};

// 조합
using RichDocument = Versioned<Timestamped<Loggable<Document>>>;

int main() {
    RichDocument doc;
    doc.setContent("Hello");
    doc.log("Document created");
    doc.setTimestamp(1234567890);
    doc.setVersion(2);
    
    std::cout << doc.getContent() << '\n';
    std::cout << "Version: " << doc.getVersion() << '\n';
}

예시 2: CRTP Mixin

#include <iostream>

template<typename Derived>
class Comparable {
public:
    bool operator!=(const Derived& other) const {
        return !(self() == other);
    }
    
    bool operator>(const Derived& other) const {
        return other < self();
    }
    
    bool operator<=(const Derived& other) const {
        return !(self() > other);
    }
    
    bool operator>=(const Derived& other) const {
        return !(self() < other);
    }
    
private:
    const Derived& self() const {
        return static_cast<const Derived&>(*this);
    }
};

class Number : public Comparable<Number> {
public:
    Number(int v) : value(v) {}
    
    bool operator==(const Number& other) const {
        return value == other.value;
    }
    
    bool operator<(const Number& other) const {
        return value < other.value;
    }
    
private:
    int value;
};

int main() {
    Number a(10), b(20);
    std::cout << (a != b) << '\n';  // 1
    std::cout << (a <= b) << '\n';  // 1
}

예시 3: 기능 선택

#include <iostream>

// 빈 베이스
class Empty {};

// Mixin들
template<typename Base>
class WithLogging : public Base {
public:
    void log(const char* msg) {
        std::cout << "[LOG] " << msg << '\n';
    }
};

template<typename Base>
class WithCaching : public Base {
public:
    void cache(int key, int value) {
        // 캐싱 로직
    }
};

// 조합 선택
template<bool EnableLogging, bool EnableCaching>
class Service : public 
    std::conditional_t<EnableLogging, WithLogging<
        std::conditional_t<EnableCaching, WithCaching<Empty>, Empty>
    >, 
    std::conditional_t<EnableCaching, WithCaching<Empty>, Empty>
    >
{
public:
    void doWork() {
        if constexpr (EnableLogging) {
            this->log("Working...");
        }
        std::cout << "Work done\n";
    }
};

int main() {
    Service<true, true> s1;   // 로깅 + 캐싱
    s1.doWork();
    
    Service<true, false> s2;  // 로깅만
    s2.doWork();
    
    Service<false, false> s3; // 둘 다 없음
    s3.doWork();
}

예시 4: 체이닝

#include <iostream>
#include <string>

template<typename Base>
class Chainable : public Base {
public:
    auto& setValue(int v) {
        value = v;
        return *this;
    }
    
    auto& setName(const std::string& n) {
        name = n;
        return *this;
    }
    
    void print() const {
        std::cout << name << ": " << value << '\n';
    }
    
private:
    int value = 0;
    std::string name;
};

class Empty {};

using Builder = Chainable<Empty>;

int main() {
    Builder b;
    b.setValue(42)
     .setName("Answer")
     .print();
}

장점

  1. 조합: 기능 자유롭게 조합
  2. 재사용: Mixin 재사용
  3. 컴파일 타임: 오버헤드 없음
  4. 타입 안전: 컴파일 타임 체크

자주 발생하는 문제

문제 1: 다이아몬드

template<typename Base>
class MixinA : public Base {};

template<typename Base>
class MixinB : public Base {};

// ❌ 다이아몬드
// class MyClass : public MixinA<Widget>, public MixinB<Widget> {};

// ✅ 체인
class MyClass : public MixinA<MixinB<Widget>> {};

문제 2: 생성자

template<typename Base>
class Mixin : public Base {
public:
    // ✅ 완벽한 전달
    template<typename... Args>
    Mixin(Args&&... args) : Base(std::forward<Args>(args)...) {}
};

문제 3: 이름 충돌

template<typename Base>
class MixinA : public Base {
public:
    void func() { std::cout << "A\n"; }
};

template<typename Base>
class MixinB : public Base {
public:
    void func() { std::cout << "B\n"; }
};

class MyClass : public MixinA<MixinB<Empty>> {
public:
    void test() {
        MixinA<MixinB<Empty>>::func();  // A
    }
};

문제 4: 타입 복잡도

// 복잡한 타입
using MyType = Mixin1<Mixin2<Mixin3<Mixin4<Base>>>>;

// ✅ 별칭
template<typename T>
using WithFeatures = Mixin1<Mixin2<Mixin3<Mixin4<T>>>>;

using MyType = WithFeatures<Base>;

사용 사례

  1. 기능 조합
  2. 선택적 기능
  3. 정책 기반 설계
  4. 빌더 패턴

FAQ

Q1: Mixin?

A: 기능 조합 패턴.

Q2: 방법?

A: 템플릿 상속.

Q3: 장점?

A: 조합, 재사용, 컴파일 타임.

Q4: 단점?

A: 타입 복잡도, 이름 충돌.

Q5: 다이아몬드?

A: 체인으로 해결.

Q6: 학습 리소스는?

A:

  • “C++ Templates”
  • “Modern C++ Design”
  • cppreference.com

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성