죽음의 다이아몬드(DDD, the Deadly Diamond of Death) - soo:bak
작성일 :
죽음의 다이아몬드(DDD, the Deadly Diamond of Death)
프로그래밍에서 다중 상속
을 사용할 때 발생할 수 있는 복잡한 문제를 지칭하는 용어.
특히, C++
와 같이 다중 상속을 허용하는 언어에서 다중 상속을 사용할 때 자주 발생하는 문제.
죽음의 다이아몬드 란?
죽음의 다이아몬드
는 클래스 상속 구조에서,
‘한 클래스가 두 개 이상의 클래스로부터 상속을 받고, 이러한 기반 클래스들이 공통의 조상을 가지고 있을 때’ 발생하는 문제이다.
예를 들어, 다음과 같은 클래스 구조에서 :
1
2
3
4
5
Base
/ \
A B
\ /
C
여기서, 클래스 C
는 기반 클래스 A
와 기반 클래스 B
로부터 상속을 받고,
클래스 A
와 클래스 B
는 기반 클래스 Base
로부터 상속을 받는다.
이 때, 만약 Base
클래스에 soobak
이라는 함수가 정의되어 있고, C
클래스의 객체에서 이 함수를 호출하려고 하면,
C
가 A
의 soobak
을 호출해야 하는지, 아니면 B
의 soobak
을 호출해야하는지 명확하지가 않다.
문제의 심화
이 문제는 단순히 ‘어떤 함수를 호출할 것인가?’ 에 대한 결정에 대해서 뿐만 아니라,
Base
클래스의 인스턴스
가 C
객체 내에 두 번 생성될 가능성을 내포하고 있다는 점에서 더욱 큰 문제가 된다.
이는, 리소스 낭비와 데이터의 불일치 등 다양한 문제들을 초래할 수 있으며, 프로그램의 예측 불가능한 동작을 일으킬 수 있다.
C++ 에서의 해결 방법
C++
에서는 이러한 문제를 해결하기 위해 가상 상속(Virtual Inheritance)
을 도입하였다.
가상 상속
을 사용하면, 공통의 조상 클래스가 다중 상속 구조에서 단 한 번만 인스턴스화 된다.
[가상 상속에 대한 예시 코드]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base {
public :
void soobak() {
// 구현...
}
};
class A : virtual public Base { // virtual 키워드로 가상 상속
// A 클래스의 구현
};
class B : virtual public Base { // virtual 키워드로 가상 상속
// B 클래스의 구현
};
class C : public A, public B{
// C 클래스의 구현
};
: 여기서, A
와 B
클래스는 Base
로부터 가상으로 상속을 받는다.
이로 인해, C
객체 내에서 Base
클래스의 단일 인스턴스만 존재하게 되며,
C
객체를 통해 Base
의 함수에 접근할 때 모호함이 해결된다.
가상 상속의 주의점
가상 상속을 사용할 때는 몇 가지 주의점이 있다.
우선, 첫 번째로 가상 상속은 일반 상속에 비하여 오버헤드가 더 크므로, 반드시 필요한 경우에만 사용해야 한다.
두 번째로, 가상 기반 클래스의 생성자
는 가장 파생된 클래스에서만 직접 호출할 수 있다
는 점이다.
이는 가상 상속 구조에서 기반 클래스의 초기화 순서를 제어하기 위함이다.
다중 상속과 함수 호출 스택 프레임
함수 호출 시, 스택 프레임은 호출된 함수의 매개변수, 지역 변수, 반환 주소 등의 정보를 포함한다.
다중 상속 구조에서 특정 함수를 호출할 때, 어떤 상위 클래스의 함수가 호출될지 명확히 구분되지 않으면,
스택 프레임의 생성과 관리에서 혼란이 발생할 수 있다.
‘죽음의 다이아몬드’ 와 스택 프레임
‘죽음의 다이아몬드’ 구조에서는 가장 하위에 있는 파생 클래스가 함수를 호출할 때,
상속 받은 두 상위 클래스가 공통의 조상으로부터 같은 함수를 상속받았다면,
해당 함수를 스택에 어떻게 쌓을지 결정하기가 어렵다.
이로 인하여, 같은 함수가 스택에 중복으로 쌓일 수 있으며, 메모리 낭비 및 실행 흐름의 혼란을 초래하게 되는 것이다.
가상 상속과 스택 프레임의 안정성
가상 상속을 사용하면, 공통의 조상 클래스는 스택 상에서 ‘단 하나의 공유된 인스턴스’ 만을 가지게 된다.
따라서, 함수 호출 시 스택 프레임이 더욱 명확하고 효율적으로 관리될 수 있도록 한다.
가상 상속을 사용할 때, 파생 클래스에서 조상 클래스의 함수를 호출하면,
컴파일러는 ‘가상 상속 테이블’ 을 참조하여 정확한 함수의 위치를 찾는다.
이 과정을 통해 스택 프레임은 정확한 함수 호출 정보를 유지할 수 있으며,
중복이나 혼란 없이 함수를 실행할 수 있다.