가상 상속
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
가상 상속은 C++에서 다이아몬드 상속 문제를 해결하기 위한 메커니즘이다. 다이아몬드 상속은 한 클래스가 같은 조상 클래스를 공유하는 두 부모 클래스를 상속받을 때 발생하며, 이로 인해 최종 파생 클래스에서 조상 클래스의 멤버에 대한 모호성이 발생한다. 가상 상속은 조상 클래스의 인스턴스를 하나만 포함하도록 하여 이 문제를 해결하며, virtual 키워드를 사용하여 선언한다. 가상 상속은 메모리 사용을 최적화하고, 다중 상속의 모호성을 줄여준다.
더 읽어볼만한 페이지
- C++ - 헤더 파일
헤더 파일은 프로그래밍 언어에서 코드 재사용성, 모듈화, 컴파일 시간 단축에 기여하며 함수 프로토타입, 변수 선언 등을 포함하고 `#include` 지시어로 소스 코드에 포함되어 사용되는 파일이다. - C++ - 소멸자 (컴퓨터 프로그래밍)
소멸자는 객체가 메모리에서 제거되기 직전에 호출되는 멤버 함수로, 객체 자원 해제 및 정리 작업을 수행하며, C++ 등 여러 언어에서 구현되고 메모리 누수 방지에 기여한다.
가상 상속 | |
---|---|
가상 상속 | |
정의 | C++에서 클래스 상속 시 발생하는 다이아몬드 상속 문제를 해결하는 기법임. |
설명 | 가상 상속을 사용하면 객체 내에 기본 클래스의 하위 객체가 두 개 이상 포함되는 것을 방지할 수 있음. |
관련 문제 | 다이아몬드 문제 |
2. 다이아몬드 상속 문제
다중 상속에서 흔히 발생하는 문제점으로, 어떤 클래스를 두 번 이상 상속받을 때 발생한다. 예를 들어, '박쥐' 클래스가 '포유류' 클래스와 '날개 달린 동물' 클래스를 상속받고, 이 두 클래스가 모두 '동물' 클래스를 상속받는 경우를 생각할 수 있다. 이 경우, '박쥐' 객체는 '동물' 클래스의 멤버를 두 개 가지게 된다. 즉, '동물' 클래스의 `eat()` 메서드를 호출할 때, '포유류'를 통한 `eat()`인지, '날개 달린 동물'을 통한 `eat()`인지 모호해진다.
이러한 상황을 다이아몬드 상속이라고 부르며, 이 문제를 해결하는 방법이 가상 상속이다.
2. 1. 문제 발생 예시 (C++ 코드)
cpp#include
#include
class A {
private:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
};
// B와 C는 가상으로 A를 상속받습니다.
class B: virtual public A { public: B():A("b"){} };
class C: virtual public A { public: C():A("c"){} };
// B와 C는 가상으로 A를 상속받으므로 각 자식에서 A를 생성해야 합니다.
// B() 및 C() 생성자는 생략할 수 있습니다.
class D: public B,C { public: D():A("d_a"),B(),C(){} };
// D() 생성자 생략
class E: public D { public: E():A("e_a"){} };
// A를 생성하지 않으면 오류가 발생합니다.
// class D: public B,C { public: D():B(),C(){} };
// A를 생성하지 않으면 오류가 발생합니다.
//class E: public D { public: E():D(){} };
int main(int argc, char ** argv){
D d;
d.test();
// 출력: "hello from A: d_a"
E e;
e.test();
// 출력: "hello from A: e_a"
}
```
위 코드는 기본 클래스 `A`에 생성자 변수 `msg`가 있고, 추가적인 조상 `E`가 손자 클래스 `D`에서 파생되는 경우를 보여준다. `A`는 `D`와 `E` 모두에서 생성되어야 한다. 변수 `msg`를 검사하면 클래스 `A`가 최종 파생 클래스 사이의 중간 파생 클래스가 아닌 파생 클래스의 직접적인 기본 클래스가 되는 방식을 보여준다.
다음과 같은 클래스 계층 구조를 생각할 수 있다.
```cpp
class Animal
{
public:
virtual void eat();
};
class Mammal : public Animal
{
public:
virtual Color getHairColor();
};
class WingedAnimal : public Animal
{
public:
virtual void flap();
};
// 박쥐는 날개가 있는 포유류이다.
class Bat : public Mammal, public WingedAnimal {};
Bat bat;
```
`bat`의 `eat()` 메서드를 호출하면 어떻게 될까? 위의 선언에서는 `bat.eat()` 호출이 모호하다. `bat.WingedAnimal.Animal::eat()`인지 `bat.Mammal.Animal::eat()`인지 알 수 없다. `Bat`(박쥐)의 `Mammal`(포유류) 성질에서의 `Animal`(동물) 성질은, `WingedAnimal`(날개 있는 동물) 성질에서의 `Animal`(동물) 성질과 같기 때문이다.
이러한 상황을 다이아몬드 상속이라고 부르며, 이를 해결하는 것이 가상 상속이다.
2. 2. 문제점 분석
`bat`의 `eat()` 메서드를 호출할 때, `bat.eat()` 호출은 모호하다는 문제가 발생한다. `bat.WingedAnimal.Animal::eat()`인지 `bat.Mammal.Animal::eat()`인지 명확하지 않기 때문이다.[1] 이러한 문제는 다중 상속의 의미론이 현실 세계를 제대로 반영하지 못하기 때문에 발생한다. 예를 들어, `Bat`(박쥐)는 `Mammal`(포유류)이면서 동시에 `WingedAnimal`(날개 달린 동물)이지만, `Bat`의 `Mammal` 특성에서의 `Animal`(동물) 특성은 `WingedAnimal` 특성에서의 `Animal` 특성과 동일하다.[1]이러한 상황을 다이아몬드 상속이라고 부르며, 이 문제를 해결하기 위해 가상 상속이 필요하다.[1]
3. 가상 상속
가상 상속은 다이아몬드 상속 문제를 해결하기 위한 방법이다. 다이아몬드 상속은 어떤 클래스가 두 개 이상의 부모 클래스를 상속받고, 그 부모 클래스들이 공통 조상 클래스를 가질 때 발생한다. 이 경우, 어떤 멤버에 접근해야 할 지 모호해지는 문제가 발생한다.
```cpp
class Animal
{
public:
virtual void eat();
};
class Mammal : public Animal
{
public:
virtual Color getHairColor();
};
class WingedAnimal : public Animal
{
public:
virtual void flap();
};
// 박쥐는 날개가 있는 포유류이다.
class Bat : public Mammal, public WingedAnimal {};
Bat bat;
```
위와 같은 코드에서 `bat.eat()`를 호출하면, `bat.WingedAnimal.Animal::eat()`인지 `bat.Mammal.Animal::eat()`인지 알 수 없어 모호하다. `Bat`는 `Mammal`이면서 동시에 `WingedAnimal`이지만, `Bat`의 `Mammal`로서의 `Animal`과 `WingedAnimal`로서의 `Animal`은 동일하다.
이러한 상황을 다이아몬드 상속이라고 부르며, 이를 해결하기 위해 가상 상속을 사용한다. 가상 상속을 사용하면, 조상 클래스가 단 한 번만 상속되도록 보장된다.
```cpp
// 두 클래스는 Animal을 가상 상속합니다.
class Mammal : public virtual Animal
{
public:
virtual Color getHairColor();
};
class WingedAnimal : public virtual Animal
{
public:
virtual void flap();
};
// 박쥐는 여전히 날개가 있는 포유류입니다.
class Bat : public Mammal, public WingedAnimal {};
```
위와 같이 `Mammal`과 `WingedAnimal` 클래스가 `Animal` 클래스를 가상 상속하면, `Bat` 클래스는 `Animal` 클래스의 인스턴스를 하나만 가지게 된다. 따라서 `Bat::eat()` 호출은 더 이상 모호하지 않다.
이는 `Mammal`과 `WingedAnimal`에 vtable 포인터를 추가하여 구현된다. `Mammal`의 시작 부분과 `Animal` 부분의 시작 부분과의 메모리 오프셋은 런타임까지 알 수 없다. 따라서 `Bat` 객체는 (vtable*, Mammal, vtable*, WingedAnimal, Bat, Animal) 형태로 메모리에 배치된다. 객체 하나에 두 개의 vtable 포인터가 있으므로 객체 크기가 커지지만, `Animal`은 하나만 존재하므로 모호성이 없어진다. 두 개의 vtable 포인터는 `Mammal`과 `WingedAnimal` 각각에서 `Animal`의 가상 상속에 대응하여 존재한다. `Bat` 타입의 모든 객체는 vtable* 값이 같지만, 각 `Bat` 타입 객체는 고유한 `Animal` 객체를 가진다.
3. 1. 가상 상속의 작동 원리
C++에서 가상 상속은 다이아몬드 상속 문제를 해결하기 위해 사용되는 메커니즘이다. 다이아몬드 상속 문제는 어떤 클래스가 두 개 이상의 부모 클래스를 상속받고, 그 부모 클래스들이 공통의 조상 클래스를 가질 때 발생한다. 이 경우 최종 파생 클래스에는 조상 클래스의 멤버가 여러 개 존재하게 되어, 어떤 멤버에 접근해야 하는지 모호해지는 문제가 발생한다.가상 상속은 이러한 모호성을 해결하기 위해, 조상 클래스가 단 한 번만 상속되도록 보장한다. 즉, `Bat` 클래스의 예시에서 `Animal` 클래스는 `Mammal`과 `WingedAnimal` 클래스에 의해 각각 한 번씩이 아닌, 공유되는 단일 인스턴스로 존재하게 된다.
```cpp
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
// Animal을 가상 상속하는 두 개의 클래스:
struct Mammal: virtual Animal {
virtual void Breathe() {}
};
struct WingedAnimal: virtual Animal {
virtual void Flap() {}
};
// 박쥐는 날개가 달린 포유류입니다.
struct Bat: Mammal, WingedAnimal {};
```
위 코드에서 `Mammal`과 `WingedAnimal` 클래스는 `Animal` 클래스를 가상 상속한다. 이렇게 하면 `Bat` 클래스는 `Animal` 클래스의 인스턴스를 하나만 가지게 된다. 따라서 `Bat::Eat()` 호출은 더 이상 모호하지 않다.
이러한 가상 상속의 구현은 vtable 포인터를 통해 이루어진다. `Mammal` 또는 `WingedAnimal` 멤버와 기본 `Animal` 멤버 간의 메모리 오프셋은 런타임에만 알 수 있기 때문에, `Bat` 객체는 ( `vpointer`, `Mammal`, `vpointer`, `WingedAnimal`, `Bat`, `Animal` ) 형태로 메모리에 배치된다. `Animal`을 가상 상속하는 상속 계층 구조별로 두 개의 vtable 포인터가 존재하며 (이 예시에서는 `Mammal`과 `WingedAnimal`에 각각 하나씩), 객체 크기는 두 포인터만큼 증가하지만, `Animal` 객체는 하나만 존재하게 되어 모호성이 해결된다.
`Bat` 유형의 모든 객체는 동일한 v포인터를 사용하지만, 각 `Bat` 객체는 고유한 `Animal` 객체를 자체적으로 포함한다. 다른 클래스, 예를 들어 `Squirrel`(다람쥐)이 `Mammal`을 상속하는 경우, `Squirrel`의 `Mammal` 부분의 v포인터는 일반적으로 `Bat`의 `Mammal` 부분의 v포인터와는 다르다. 하지만 `Squirrel` 클래스가 `Bat`과 크기가 같으면 동일할 수도 있다.
3. 2. 가상 상속 선언 방법 (C++ 코드)
C++에서 가상 상속은 `virtual` 키워드를 사용하여 선언한다. 이 키워드는 상속되는 기본 클래스(base class)의 인스턴스가 파생 클래스(derived class)에서 단 하나만 존재하도록 보장한다.```cpp
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
// Animal을 가상 상속하는 두 개의 클래스:
struct Mammal : virtual Animal {
virtual void Breathe() {}
};
struct WingedAnimal : virtual Animal {
virtual void Flap() {}
};
// 박쥐는 날개가 달린 포유류입니다.
struct Bat : Mammal, WingedAnimal {};
```
위 코드에서 `Mammal`과 `WingedAnimal` 클래스는 `Animal` 클래스를 가상 상속한다. `Bat` 클래스는 `Mammal`과 `WingedAnimal` 클래스를 다중 상속한다. 가상 상속 덕분에 `Bat` 객체는 단 하나의 `Animal` 객체만을 포함한다. 만약 가상 상속을 사용하지 않았다면 `Bat` 클래스에서 `Eat()` 함수를 호출할 때 어떤 `Animal`의 `Eat()` 함수를 호출해야 하는지 모호해진다. (`bat.WingedAnimal.Animal::eat()`인지 `bat.Mammal.Animal::eat()`인지) 하지만 가상상속을 통해서 `Bat::WingedAnimal`의 `Animal` 부분은 `Bat::Mammal`에서 사용되는 `Animal`과 동일해진다. 즉, 하나의 `Bat` 인스턴스에는 하나의 `Animal`만 대응하므로 `Bat::eat()` 호출이 모호해지지 않는다.[1]
가상 상속은 다이아몬드 상속 문제를 해결하는 데 사용된다.
가상 상속을 구현하기 위해 `Mammal`과 `WingedAnimal`에는 vtable 포인터가 추가된다. `Mammal`의 시작 부분과 `Animal` 시작 부분과의 메모리 오프셋은 런타임까지 알 수 없다. 따라서 `Bat`는 (vtable*, Mammal, vtable*, WingedAnimal, Bat, Animal) 형태가 된다. 객체 하나에 vtable 포인터가 두 개 있으므로 객체 크기는 커지지만, `Animal`은 하나만 존재하므로 모호성은 없어진다. 두 개의 vtable 포인터는 `Mammal`과 `WingedAnimal` 각각에서 `Animal`을 가상 상속하는 것에 대응하여 존재한다.[1]
3. 3. 가상 상속의 장점
가상 상속은 다중 상속에서 발생할 수 있는 모호성을 제거하고 메모리 사용의 효율성을 높이는 장점을 제공한다.C++ 코드 예시에서 `Bat` 클래스는 `Mammal`과 `WingedAnimal` 클래스로부터 다중 상속을 받는다. 이때 `Mammal`과 `WingedAnimal` 클래스는 모두 `Animal` 클래스를 가상 상속한다.[1]
```cpp
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
// Animal을 가상 상속하는 두 개의 클래스:
struct Mammal : virtual Animal {
virtual void Breathe() {}
};
struct WingedAnimal : virtual Animal {
virtual void Flap() {}
};
// 박쥐는 날개가 달린 포유류입니다.
struct Bat : Mammal, WingedAnimal {};
```
`Bat` 객체는 `Animal` 클래스의 인스턴스를 단 하나만 가지게 된다. 즉, `Bat::Eat()`과 같이 `Animal` 클래스의 멤버 함수를 호출할 때 어떤 `Animal` 인스턴스의 함수를 호출해야 하는지에 대한 모호성이 발생하지 않는다.[1]
이는 `Mammal`과 `WingedAnimal`이 `Animal` 부모 클래스의 단일 인스턴스를 공유하도록 설계되었기 때문이다. 이러한 공유는 `Mammal` 또는 `WingedAnimal` 멤버와 파생 클래스 내의 기본 `Animal` 멤버 간의 메모리 오프셋을 기록하는 방식으로 구현된다. 이 오프셋은 런타임에 결정되므로, `Bat` 클래스는 두 개의 vtable 포인터를 가지게 된다. 하나는 `Mammal`에, 다른 하나는 `WingedAnimal`에 대한 것이다. 객체 크기는 두 포인터 크기만큼 증가하지만, `Animal` 인스턴스는 하나만 존재하므로 메모리 사용이 효율적이며, 모호성이 제거된다.[1]
`Bat` 유형의 모든 객체는 동일한 v포인터를 사용하지만, 각 `Bat` 객체는 고유한 `Animal` 객체를 포함한다. `Squirrel`과 같이 `Mammal`을 상속하는 다른 클래스의 경우, `Squirrel`의 `Mammal` 부분에 있는 v포인터는 일반적으로 `Bat`의 `Mammal` 부분에 있는 v포인터와 다르다. 그러나 `Squirrel` 클래스가 `Bat`과 크기가 같으면 동일할 수도 있다.[1]
4. 가상 상속과 관련된 추가 고려 사항
C++다중 상속으로 인해 발생할 수 있는 여러 문제점을 해결하는 데 유용한 가상 상속은 몇 가지 추가적인 고려 사항이 있다.
4. 1. 순수 가상 함수와 가상 상속
기반 클래스에 순수 가상 메서드가 정의되어 있을 때, 파생 클래스가 기반 클래스를 가상으로 상속하는 경우에는 해당 파생 클래스에서 순수 가상 메서드를 정의할 필요가 없다. 그러나 파생 클래스가 기반 클래스를 가상으로 상속하지 않는 경우에는 모든 가상 메서드를 정의해야 한다. 아래는 예시 코드이다.```c++
#include
#include
class A {
protected:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
virtual void pure_virtual_test() = 0;
};
// B, C는 A를 가상으로 상속하므로 순수 가상 메서드 pure_virtual_test를 정의할 필요가 없습니다.
class B: virtual public A { public: B(std::string x):A("b"){} };
class C: virtual public A { public: C(std::string x):A("c"){} };
// B, C는 A를 가상으로 상속하므로 각 자식 클래스에서 A를 생성해야 합니다.
// 그러나 D는 B, C를 가상으로 상속하지 않으므로 A의 순수 가상 메서드를 *반드시 정의해야 합니다*.
class D: public B,C {
public:
D(std::string x):A("d_a"),B("d_b"),C("d_c"){}
void pure_virtual_test() override { std::cout<<"pure virtual hello from: "<<_msg <<"\n"; }
};
// 부모 클래스에서 순수 가상 메서드를 정의한 후에는 다시 정의할 필요가 없습니다.
class E: public D {
public:
E(std::string x):A("e_a"),D("e_d"){}
};
int main(int argc, char ** argv){
D d("d");
d.test(); // hello from A: d_a
d.pure_virtual_test(); // pure virtual hello from: d_a
E e("e");
e.test(); // hello from A: e_a
e.pure_virtual_test(); // pure virtual hello from: e_a
}
```
위 코드는 여기에서 대화식으로 살펴볼 수 있다.
4. 2. 생성자 호출과 가상 상속
가상 상속에서 생성자 호출 순서는 일반적인 상속과 다소 차이가 있다. 가상 기본 클래스는 가장 하위 파생 클래스(most derived class)의 생성자에 의해 초기화된다. 아래의 예시 코드에서 클래스 `D`와 `E`는 모두 가상 기본 클래스 `A`를 가진다. 따라서 `D`의 생성자는 `A`의 생성자를 직접 호출하여 초기화하고, `E`의 생성자 역시 `A`의 생성자를 직접 호출하여 초기화한다.[1]```c++
class D: public B, C { public: D(): A("d_a"), B(), C() {} };
class E: public D { public: E(): A("e_a") {} };
```
`D`의 생성자 `D(): A("d_a"), B(), C() {}`는 `A`를 `"d_a"`로 초기화하고, `B`와 `C`의 기본 생성자를 호출한다. `E`의 생성자 `E(): A("e_a") {}`는 `A`를 `"e_a"`로 초기화한다. `D`의 생성자는 `B`와 `C`의 생성자를 호출하지만, `B`와 `C`가 가상 상속을 사용하므로 `A`의 생성자는 `D`에서 한 번만 호출된다.[1]
만약 가장 하위 파생 클래스에서 가상 기본 클래스의 생성자를 명시적으로 호출하지 않으면, 가상 기본 클래스의 기본 생성자가 호출된다. 만약 가상 기본 클래스에 기본 생성자가 없다면 컴파일 오류가 발생한다.[1]
아래의 코드에서 주석 처리된 부분처럼 `A`를 생성하지 않으면 오류가 발생한다.[1]
```c++
// A를 생성하지 않으면 오류가 발생합니다.
// class D: public B,C { public: D():B(),C(){} };
// A를 생성하지 않으면 오류가 발생합니다.
//class E: public D { public: E():D(){} };
```
이러한 가상 상속에서의 생성자 호출 방식은 다중 상속 시 생성자 호출의 복잡성을 줄여주는 효과가 있다.
4. 3. 다중 상속과 가상 상속의 복합적인 사용 예시 (C++ 코드)
cpp#include
#include
class A {
private:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
};
// B와 C는 A를 가상 상속한다.
class B: virtual public A { public: B():A("b"){} };
class C: virtual public A { public: C():A("c"){} };
// B와 C는 A를 가상 상속하므로 각 자식 클래스에서 A를 생성해야 한다.
// B()와 C() 생성자는 생략할 수 있다.
class D: public B,C { public: D():A("d_a"),B(),C(){} };
// D() 생성자 생략
class E: public D { public: E():A("e_a"){} };
// A를 생성하지 않으면 오류가 발생한다.
// class D: public B,C { public: D():B(),C(){} };
// A를 생성하지 않으면 오류가 발생한다.
//class E: public D { public: E():D(){} };
int main(int argc, char ** argv){
D d;
d.test();
// 출력: "hello from A: d_a"
E e;
e.test();
// 출력: "hello from A: e_a"
}
```
위 코드는 다중 상속과 가상 상속이 함께 사용되는 복잡한 상속 관계를 보여주는 예시다.
- `A` 클래스는 문자열 멤버 변수 `_msg`와 `test()` 메서드를 가진다.
- `B`와 `C` 클래스는 `A`를 가상 상속한다. 즉, `B`와 `C`를 상속하는 클래스는 `A`의 인스턴스를 공유한다.
- `D` 클래스는 `B`와 `C`를 다중 상속한다. `B`와 `C`가 `A`를 가상 상속하므로, `D`는 `A`의 인스턴스를 하나만 가진다. `D`의 생성자는 `A`의 생성자를 명시적으로 호출하여 `_msg` 값을 초기화한다.
- `E` 클래스는 `D`를 상속한다. `E`의 생성자도 `A`의 생성자를 명시적으로 호출한다.
`main` 함수에서 `D`와 `E` 클래스의 객체를 생성하고 `test()` 메서드를 호출하면, 각 객체에 해당하는 `A` 인스턴스의 `_msg` 값이 출력된다.
이 예제는 https://godbolt.org/z/ed95e9Yvc 여기서 대화식으로 탐색할 수 있다.
```c++
#include
#include
class A {
protected:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
virtual void pure_virtual_test() = 0;
};
// B, C는 A를 가상 상속하므로 순수 가상 메서드 pure_virtual_test를 정의할 필요가 없다.
class B: virtual public A { public: B(std::string x):A("b"){} };
class C: virtual public A { public: C(std::string x):A("c"){} };
// B, C는 A를 가상 상속하므로 각 자식 클래스에서 A를 생성해야 한다.
// 그러나 D는 B, C를 가상 상속하지 않으므로 A의 순수 가상 메서드를 *반드시 정의해야 한다*.
class D: public B,C {
public:
D(std::string x):A("d_a"),B("d_b"),C("d_c"){}
void pure_virtual_test() override { std::cout<<"pure virtual hello from: "<<_msg <<"\n"; }
};
// 부모 클래스에서 순수 가상 메서드를 정의한 후에는 다시 정의할 필요가 없다.
class E: public D {
public:
E(std::string x):A("e_a"),D("e_d"){}
};
int main(int argc, char ** argv){
D d("d");
d.test(); // hello from A: d_a
d.pure_virtual_test(); // pure virtual hello from: d_a
E e("e");
e.test(); // hello from A: e_a
e.pure_virtual_test(); // pure virtual hello from: e_a
}
```
위 코드는 기반 클래스(`A`)에 순수 가상 메서드(`pure_virtual_test`)가 정의되어 있고, 파생 클래스에서 이를 어떻게 처리하는지 보여준다.
- `B`와 `C`는 `A`를 가상 상속하므로 `pure_virtual_test`를 정의할 필요가 없다.
- `D`는 `B`와 `C`를 가상 상속하지 않으므로, `A`의 순수 가상 메서드를 반드시 정의해야 한다.
- `E`는 `D`를 상속하고, `D`에서 이미 `pure_virtual_test`를 정의했으므로 다시 정의할 필요가 없다.
이 예제는 https://godbolt.org/z/3nrejWfEW 여기에서 대화식으로 살펴볼 수 있다.
참조
[1]
웹사이트
Solving the Diamond Problem with Virtual Inheritance
http://www.cprogramm[...]
2010-03-08
[2]
웹사이트
C++/What is virtual inheritance?
http://en.allexperts[...]
2004-02-14
[3]
웹인용
Solving the Diamond Problem with Virtual Inheritance
http://www.cprogramm[...]
2010-03-08
[4]
웹인용
C++/What is virtual inheritance?
http://en.allexperts[...]
2004-02-14
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com