C++ 다중 상속과 가상 상속
다중 상속 기본
섹션 제목: “다중 상속 기본”class Flyable { public: virtual void fly() {} };class Swimmable{ public: virtual void swim() {} };
class Duck : public Flyable, public Swimmable { void fly() override { /* ... */ } void swim() override { /* ... */ }};
Duck d;d.fly();d.swim();
// 각 기반 클래스 포인터로 변환Flyable* fp = &d; // 오프셋 0Swimmable* sp = &d; // 내부적으로 주소 오프셋 적용메모리 레이아웃:
[ vptr_Flyable | Flyable 데이터 | vptr_Swimmable | Swimmable 데이터 | Duck 데이터 ]다이아몬드 문제
섹션 제목: “다이아몬드 문제”class Animal { public: int age; };class Dog : public Animal {};class Cat : public Animal {};class Chimera: public Dog, public Cat {}; // age가 두 개!
Chimera c;// c.age; // 오류: 모호함 (Dog::age? Cat::age?)c.Dog::age = 5; // 명시적 지정c.Cat::age = 3;Chimera는 Animal의 데이터를 두 벌 가집니다.
virtual 상속으로 해결
섹션 제목: “virtual 상속으로 해결”virtual 상속은 공유 기반 클래스를 한 번만 포함시킵니다.
class Animal { public: int age = 0; };class Dog : virtual public Animal {};class Cat : virtual public Animal {};class Chimera : public Dog, public Cat {};
Chimera c;c.age = 5; // 오류 없음 — Animal은 하나c.Dog::age = 5; // 같은 것 가리킴메모리 레이아웃 (virtual 상속):
[ vptr_Dog | Dog 데이터 | vptr_Cat | Cat 데이터 | Animal 데이터(공유) ]Virtual 상속은 vbase 포인터(가상 기반 클래스 포인터) 추가로 객체 크기와 접근 비용이 증가합니다.
생성자 호출 순서 (virtual 상속)
섹션 제목: “생성자 호출 순서 (virtual 상속)”struct A { A(int x) : x(x) {} int x; };struct B : virtual A { B() : A(1) {} };struct C : virtual A { C() : A(2) {} };struct D : B, C { D() : A(10) {} }; // D가 A를 직접 초기화Virtual 기반 클래스는 **가장 파생된 클래스(most-derived)**가 직접 초기화합니다. B, C의 A(1), A(2) 호출은 D에서 무시됩니다.
믹스인(Mixin) 패턴
섹션 제목: “믹스인(Mixin) 패턴”다중 상속을 활용한 기능 합성 패턴입니다.
template<typename Base>class Serializable : public Base {public: std::string serialize() const { // Base의 필드를 JSON으로 변환 return "{}"; }};
template<typename Base>class Loggable : public Base {public: void log(std::string_view msg) const { std::cout << "[LOG] " << msg << '\n'; }};
class Entity { public: int id; };using LoggableSerializableEntity = Serializable<Loggable<Entity>>;
LoggableSerializableEntity e;e.log("created");e.serialize();인터페이스 다중 상속 (권장 패턴)
섹션 제목: “인터페이스 다중 상속 (권장 패턴)”// 순수 가상 함수만 있는 인터페이스 → 데이터 없음 → 다이아몬드 문제 없음class IRenderable { public: virtual void render() = 0; virtual ~IRenderable() = default; };class IUpdatable { public: virtual void update(float dt) = 0; virtual ~IUpdatable() = default; };
class Sprite : public IRenderable, public IUpdatable { void render() override {} void update(float dt) override {}};- 다중 상속 시 동일 기반 클래스가 두 번 포함되면 다이아몬드 문제 발생
virtual상속으로 공유 기반 클래스를 단일화- Virtual 상속은 생성자 규칙이 복잡하고 성능 오버헤드가 있으므로 꼭 필요할 때만 사용
- 데이터 없는 인터페이스 다중 상속은 안전하고 일반적인 패턴
- Mixin 패턴은 CRTP와 결합해 정적 다형성으로 성능 최적화 가능