다중 기본 클래스

클래스는 둘 이상의 기본 클래스에서 파생될 수 있습니다. 여러 상속 모델(클래스가 둘 이상의 기본 클래스에서 파생되는 경우)에서 기본 클래스는 기본 목록 문법 요소를 사용하여 지정됩니다. 예를 들어 CollectionOfBookCollection에서 파생된 Book에 대한 클래스 선언을 지정할 수 있습니다.

// deriv_MultipleBaseClasses.cpp
// compile with: /LD
class Collection {
};
class Book {};
class CollectionOfBook : public Book, public Collection {
    // New members
};

생성자와 소멸자가 호출되는 특정 경우를 제외하고는 기본 클래스가 지정된 순서는 중요하지 않습니다. 이러한 경우 기본 클래스가 지정되는 순서는 다음에 영향을 줍니다.

  • 생성자가 호출되는 순서입니다. 코드가 BookCollectionOfBook 부분을 의존하여 Collection 파트 전에 초기화되는 경우 사양의 순서는 중요합니다. 초기화는 클래스가 기본 목록에 지정된 순서대로 이루어집니다.

  • 정리하기 위해 소멸자를 호출하는 순서입니다. 다른 부품이 소멸될 때 클래스의 특정 "부품"이 있어야 하는 경우 순서가 중요합니다. 소멸자는 기본 목록에 지정된 클래스의 역순으로 호출됩니다.

    참고 항목

    기본 클래스의 사양 순서는 클래스의 메모리 레이아웃에 영향을 줍니다. 메모리에 있는 기본 멤버의 순서에 따라 모든 프로그래밍 의사를 결정하지 마십시오.

기본 목록을 지정할 때 동일한 클래스 이름을 두 번 이상 지정할 수 없습니다. 그러나 클래스가 파생 클래스에 대한 간접 기반이 두 번 이상 될 수 있습니다.

가상 기본 클래스

클래스는 한 번 이상 파생 클래스에 대한 간접 기본 클래스일 수 있으므로 C++는 이런 기본 클래스가 작동하는 방식을 최적화하는 방법을 제공합니다. 가상 기본 클래스는 다수의 형식 상속을 사용하는 클래스 계층에 모호성이 발생하지 않도록 공간을 절약하는 방법을 제공합니다.

각 비가상 개체에는 기본 클래스에 정의된 데이터 멤버의 복사본이 있습니다. 이 복제는 공간을 낭비하고 기본 클래스 멤버에 액세스할 때마다 기본 클래스 멤버의 어떤 사본을 원하는지 지정할 것을 요구합니다.

기본 클래스를 가상 기본으로 지정하면, 데이터 멤버가 중복되지 않으면서 한 번 이상 간접 기본으로 작동할 수 있습니다. 해당 데이터 멤버의 단일 복사본은 해당 복사본을 가상 기본 클래스로 사용하는 모든 기본 클래스에서 공유합니다.

가상 기본 클래스 virtual 를 선언할 때 키워드(keyword) 파생 클래스의 기본 목록에 나타납니다.

다음 그림에서 시뮬레이션된 점심 줄을 보여주는 클래스 계층 구조를 고려합니다.

Diagram of a simulated lunch line.

기본 클래스는 Queue입니다. 점원 큐와 점심 큐는 모두 큐에서 상속됩니다. 마지막으로, 점심 계산원 큐는 계산원 큐와 점심 큐 모두에서 상속됩니다.

시뮬레이션된 점심 회선 그래프

그림에서 QueueCashierQueueLunchQueue에 대한 기본 클래스입니다. 하지만 두 클래스가 결합되어 LunchCashierQueue를 형성할 경우 새 클래스에 형식이 Queue인 두 하위 개체가 포함되는데 하나는 CashierQueue의 하위 개체이고 하나는 LunchQueue의 하위 개체인 문제가 발생할 수 있습니다. 다음 그림에서는 개념적 메모리 레이아웃(실제 메모리 레이아웃이 최적화될 수 있음)을 보여줍니다.

Diagram of a simulated lunch line object.

그림에는 계산원 큐와 점심 큐의 두 하위 개체가 있는 점심 계산기 큐 개체가 표시됩니다. 점원 큐와 점심 큐 모두 큐 하위 개체가 포함되어 있습니다."

시뮬레이션된 점심 줄 개체

개체에는 두 개의 Queue 하위 개체가 LunchCashierQueue 있습니다. 다음 코드에서는 Queue를 가상 기본 클래스로 선언합니다.

// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};

virtual 키워드(keyword) 하위 개체 Queue 의 복사본이 하나만 포함되도록 합니다(다음 그림 참조).

Diagram of a simulated lunch line object, with virtual base classes depicted.

다이어그램은 점원 큐 하위 개체와 점심 큐 하위 개체를 포함하는 점심 계산기 큐 개체를 보여 줍니다. 점원 큐와 점심 큐는 모두 동일한 큐 하위 개체를 공유합니다.

가상 기본 클래스를 사용하는 시뮬레이션된 런치 라인 개체

클래스에는 주어진 형식의 가상 구성 요소와 비가상 구성 요소가 둘 다 있을 수 있습니다. 이 문제는 다음 그림에 설명된 조건에서 발생합니다.

Diagram of virtual and non virtual components of a class.

다이어그램은 큐 기본 클래스를 보여줍니다. 점원 큐 클래스 및 점심 큐 클래스는 큐에서 사실상 상속됩니다. 세 번째 클래스인 테이크아웃 큐는 큐에서 사실상 상속하지 않습니다. 점심 계산원 큐는 점원 큐와 점심 큐 모두에서 상속됩니다. 점심 테이크아웃 계산원 큐는 점심 계산원 큐와 테이크아웃 큐 모두에서 상속됩니다.

동일한 클래스의 가상 및 비이상 구성 요소

그림에서 CashierQueueLunchQueueQueue를 가상 기본 클래스로 사용합니다. 하지만 TakeoutQueueQueue를 가상 기본 클래스가 아니라 기본 클래스로 지정합니다. 따라서, LunchTakeoutCashierQueue는 형식이 Queue인 두 개의 하위 개체를 가지는데, 하나는 LunchCashierQueue를 포함하는 상속 경로의 하위 개체이고 하나는 TakeoutQueue를 포함하는 경로의 하위 개체입니다. 이는 다음 그림에 설명되어 있습니다.

Diagram of the object layout for virtual and non virtual inheritance.

점심 테이크아웃 계산기 큐 개체에는 테이크아웃 큐(큐 하위 개체 포함) 및 점심 계산기 큐의 두 하위 개체가 포함되어 있습니다. 점심 점원 큐 하위 개체에는 점원 큐 하위 개체와 점심 큐 하위 개체가 포함되어 있으며 둘 다 큐 하위 개체를 공유합니다.

가상 및 비이상 상속이 있는 개체 레이아웃

참고 항목

가상 상속은 비가상 상속과 비교했을 때 상당한 크기의 혜택을 제공하지만, 추가 처리 오버헤드가 발생할 수 있습니다.

파생 클래스가 가상 기본 클래스에서 상속하는 가상 함수를 재정의하고 파생된 기본 클래스의 생성자 또는 소멸자가 가상 기본 클래스에 대한 포인터를 사용하여 해당 함수를 호출하는 경우 컴파일러는 다른 숨겨진 "vtordisp" 필드를 가상 베이스가 있는 클래스에 도입할 수 있습니다. /vd0 컴파일러 옵션은 숨겨진 vtordisp 생성자/소멸자 변위 멤버의 추가를 표시하지 않습니다. 컴파일러 옵션(기본값)을 /vd1 사용하면 필요한 위치에 사용할 수 있습니다. 모든 클래스 생성자와 소멸자가 가상 함수를 가상으로 호출한다고 확신하는 경우에만 vtordisps를 끕니다.

/vd 컴파일러 옵션은 전체 컴파일 모듈에 영향을 줍니다. pragma를 vtordisp 사용하여 클래스별로 필드를 표시하지 않은 다음 다시 활성화 vtordisp 합니다.

#pragma vtordisp( off )
class GetReal : virtual public { ... };
\#pragma vtordisp( on )

이름 모호성

다중 상속이 발생하면 2개 이상의 경로에서 이름이 상속될 가능성이 있습니다. 이러한 경로의 클래스 멤버 이름이 반드시 고유하지는 않습니다. 이러한 이름 충돌을 "모호성"이라고 합니다.

클래스 멤버를 참조하는 식은 모호하지 않은 참조를 만들어야 합니다. 다음 예제에서는 모호성이 전개되는 방법을 보여 줍니다.

// deriv_NameAmbiguities.cpp
// compile with: /LD
// Declare two base classes, A and B.
class A {
public:
    unsigned a;
    unsigned b();
};

class B {
public:
    unsigned a();  // class A also has a member "a"
    int b();       //  and a member "b".
    char c;
};

// Define class C as derived from A and B.
class C : public A, public B {};

앞의 클래스 선언을 고려할 때 다음과 같은 코드는 in 또는 Bin A 을 참조하는지 여부 b 가 불분명하기 b 때문에 모호합니다.

C *pc = new C;

pc->b();

위의 예제를 살펴 보십시오. 이름은 a 클래스와 클래스 AB모두의 멤버이므로 컴파일러는 호출할 함수를 지정하는 것을 a 식별할 수 없습니다. 함수, 개체, 형식 또는 열거자를 2개 이상 참조할 수 있는 경우 멤버에 대한 액세스가 모호합니다.

컴파일러는 다음 순서대로 테스트하여 모호성을 검색합니다.

  1. 앞에서 설명한 대로 이름에 대한 액세스가 모호할 경우 오류 메시지가 생성됩니다.

  2. 오버로드된 함수가 명확하지 않으면 해결됩니다.

  3. 이름에 대한 액세스가 멤버 액세스 권한을 위반하는 경우 오류 메시지가 생성됩니다. (자세한 내용은 를 참조하세요 .멤버 액세스 제어.)

상속을 통해 식에 모호성이 생기면 클래스 이름으로 문제의 이름을 정규화하여 수동으로 해결할 수 있습니다. 모호성이 발생하지 않도록 앞의 예제를 적절하게 컴파일하려면 다음 코드를 사용하십시오.

C *pc = new C;

pc->B::a();

참고 항목

C가 선언되면 BC의 범위에서 참조될 경우 오류가 발생할 수 있습니다. 그러나 B의 범위에 실제로 C의 정규화되지 않은 참조가 생길 때까지는 오류가 발생하지 않습니다.

우위

상속 그래프를 통해 둘 이상의 이름(함수, 개체 또는 열거자)에 연결할 수 있습니다. 이러한 경우는 비가상 기본 클래스에서 모호한 것으로 간주됩니다. 이름 중 하나가 다른 이름을 "지배"하지 않는 한 가상 기본 클래스에도 모호합니다.

두 클래스에 모두 정의되고 한 클래스가 다른 클래스에서 파생된 경우 이름이 다른 이름을 지배합니다. 우위를 차지하는 이름은 파생 클래스에 있는 이름입니다. 이 이름은 모호성이 다른 방식으로 발생하지 않은 경우 다음 예제와 같이 사용됩니다.

// deriv_Dominance.cpp
// compile with: /LD
class A {
public:
    int a;
};

class B : public virtual A {
public:
    int a();
};

class C : public virtual A {};

class D : public B, public C {
public:
    D() { a(); } // Not ambiguous. B::a() dominates A::a.
};

모호한 변환

포인터 또는 참조 형식에서 클래스 형식으로 명시적 및 암시적으로 변환하면 모호성이 발생할 수 있습니다. 아래에 나와 있는 포인터에서 기본 클래스로의 모호한 변환 그림에서는 다음을 보여 줍니다.

  • D 형식 개체의 선언

  • 해당 개체에 address-of 연산자(&)를 적용하는 효과입니다. address-of 연산자는 항상 개체의 기본 주소를 제공합니다.

  • 주소 연산자를 사용하여 얻은 포인터를 기본 클래스 형식 A로 명시적으로 변환하는 경우의 효과. 개체의 주소를 형식 A* 으로 강제 변환해도 항상 컴파일러에 선택할 형식 A 의 하위 개체에 대한 충분한 정보가 제공되지는 않습니다. 이 경우 두 개의 하위 개체가 존재합니다.

Diagram showing how the conversion of pointers to base classes can be ambiguous.

다이어그램은 먼저 상속 계층 구조를 보여 줍니다. A는 기본 클래스입니다. B와 C는 A에서 상속됩니다. D는 B와 C에서 상속됩니다. 그런 다음 개체 D에 대한 메모리 레이아웃이 표시됩니다. D에는 B(하위 개체 A 포함) 및 C(하위 개체 A 포함)의 세 가지 하위 개체가 있습니다. 코드 &d는 하위 개체 B의 A를 가리킵니다. 코드(* A) &d는 하위 개체 B와 하위 개체 C를 모두 가리킵니다.

기본 클래스에 대한 포인터의 모호한 변환

형식 A* 으로의 변환(포인터) A은 올바른 형식 A 의 하위 개체를 식별할 방법이 없기 때문에 모호합니다. 다음과 같이 사용할 하위 개체를 명시적으로 지정하여 모호성을 방지할 수 있습니다.

(A *)(B *)&d       // Use B subobject.
(A *)(C *)&d       // Use C subobject.

모호성 및 가상 기본 클래스

가상 기본 클래스를 사용하는 경우 다중 상속 경로를 통해 함수, 개체, 형식 및 열거자에 도달할 수 있습니다. 기본 클래스의 인스턴스는 하나뿐이므로 이러한 이름에 액세스할 때 모호성이 없습니다.

다음 그림에서는 가상 및 비가상 상속을 사용하여 개체를 구성하는 방법을 보여 줍니다.

Diagram showing virtual derivation and nonvirtual derivation.

다이어그램은 먼저 상속 계층 구조를 보여 줍니다. A는 기본 클래스입니다. B와 C는 사실상 A에서 상속됩니다. D는 사실상 B와 C에서 상속됩니다. 그런 다음 D의 레이아웃이 표시됩니다. D에는 하위 개체 A를 공유하는 하위 개체 B 및 C가 포함됩니다. 그런 다음 레이아웃은 비가상 상속을 사용하여 동일한 계층 구조가 파생된 것처럼 표시됩니다. 이 경우 D에는 하위 개체 B 및 C가 포함됩니다. B와 C 모두 하위 개체 A의 자체 복사본을 포함합니다.

가상 및 비비상 파생

이 그림에서 비가상 기본 클래스를 통해 A 클래스의 임의 멤버에 액세스하면 모호성이 발생합니다. 컴파일러는 B에 연결된 하위 개체를 사용할지, 아니면 C에 연결된 하위 개체를 사용할지를 설명하는 정보를 가지고 있지 않습니다. 그러나 가상 기본 클래스로 지정된 경우 A 어떤 하위 개체에 액세스하고 있는지 의심의 여지가 없습니다.

참고 항목

상속