콘텐츠로 이동

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; // 오프셋 0
Swimmable* 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;

ChimeraAnimal의 데이터를 두 벌 가집니다.

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 포인터(가상 기반 클래스 포인터) 추가로 객체 크기와 접근 비용이 증가합니다.

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에서 무시됩니다.

다중 상속을 활용한 기능 합성 패턴입니다.

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와 결합해 정적 다형성으로 성능 최적화 가능