class Base {
int x;
public:
Base(int x = 0) : x(x) {}
void Print() const { std::cout << x << ' '; }
};
class Base1 : virtual public Base {
int x1;
public:
Base1(int x1 = 0) : x1(x1) {};
void Print() const { std::cout << x1 << ' '; }
};
→ virtual 상속은 다중 상속 시 중복 상속 문제(Diamond Problem)를 방지하기 위해 사용된다.
class Base {
int x;
};
class Base1 : public Base { };
class Base2 : public Base { };
class Derived : public Base1, public Base2 { };
이런 구조를 다이아몬드 상속 구조라고 합니다. Derived 클래스는 Base를 두 번 상속받게 되어, 내부적으로 Base의 복사본이 2개 존재한다.
Derived 객체 안에는 Base가 두 개 존재해서, 어떤 Base 멤버에 접근할 때 모호성이 생긴다.
Derived d;
d.x = 5; // 에러! Base가 두 개라서 어떤 x인지 모름
class Base1 : virtual public Base { };
class Base2 : virtual public Base { };
class Derived : public Base1, public Base2 { };
virtual 키워드를 붙이면, Derived 클래스는 Base의 인스턴스를 한 번만 상속받습니다. 즉, 공유합니다.
| 항목 | virtual 안 씀 | virtual 씀 |
|---|---|---|
Base 인스턴스 수 | 상속받은 만큼 (중복됨) | 1개만 공유 |
| 다이아몬드 상속 문제 | 발생 (모호함) | 발생하지 않음 |
| 생성자 호출 방식 | 각각 호출 | 하나만 호출, 그러나 명시적으로 직접 호출 필요 |
처음부터 Base를 두 번 상속받을 일을 안 만들면 되는 거 아닌가?
→ Base를 두 번 상속받는 다이아몬드 상속 구조는 그냥 일부러 그렇게 하는 게 아니라, 두 클래스(A, B)가 공통된 기능을 갖기 위해 Base를 상속하고, 그 두 클래스를 조합해서 새로운 기능을 만들고자 할 때 자연스럽게 생길 수 있습니다.
Ex) Student와 Employee는 각각 Person을 상속
→ 그래서 virtual을 사용하면 Person은 딱 1개만 유지됨
virtual 상속은 주로 다중 상속에서 동일한 기반 클래스를 공유하기 위해 사용합니다.
단일 상속에서는 불필요하며, 다중 상속 시 안전하게 Base 클래스 하나만 공유하고 싶을 때 사용합니다.
필요할 때 쓰는 안전장치라고 생각하면 됩니다.
class Animal {
public:
virtual void Speak() { std::cout << "Animal\n"; }
};
class Dog : public Animal {
public:
void Speak() override { std::cout << "Dog\n"; }
};
Animal* a = new Dog();
a->Speak(); // "Dog"
→ virtual 함수는 다형성(polymorphism)을 위해 사용된다. 함수를 자식 클래스에서 오버라이딩하고, 부모 포인터로 호출해도 자식 함수가 실행되게 합니다.