답변:
다중 상속 (MI로 약칭 됨)은 냄새가납니다 . 이는 일반적으로 나쁜 이유 때문에 수행되었으며 관리자의 얼굴로 날아갈 것입니다.
상속의 경우에도 마찬가지이므로 다중 상속의 경우에는 더욱 그렇습니다.
객체가 실제로 다른 객체를 상속해야합니까? A Car
는 Engine
직장에서 또는에서 상속받을 필요가 없습니다 Wheel
. A Car
는 Engine
4 개 Wheel
입니다.
컴포지션 대신 이러한 문제를 해결하기 위해 다중 상속을 사용하는 경우 잘못된 작업이 있습니다.
일반적으로, 당신은 클래스가 A
다음 B
과 C
에서 모두 상속을 A
. 그리고 (왜 나에게 묻지 말고) 누군가 D
가 B
와 에서 상속해야한다고 결정 합니다 C
.
8 8 년 만에 이런 종류의 문제가 두 번 발생했으며 다음과 같은 이유로 인해 재미 있습니다.
D
모두 B
와 에서 모두 상속 받지 않아야 함).C
C
A
가 손자 클래스에 두 번 존재 했기 때문에 유지 보수 담당자가 지불 한 금액이므로 D
하나의 부모 필드 A::field
를 업데이트하면 두 번 업데이트하고 (통과 B::field
및 C::field
) 무언가 잘못되고 충돌이 발생했습니다. (에서 포인터를 새로 작성 B::field
하고 삭제 C::field
...)상속을 제한하기 위해 C ++에서 virtual 키워드를 사용하면 이것이 원하는 것이 아니라면 위에서 설명한 이중 레이아웃을 피할 수 있지만 어쨌든 내 경험상 아마도 뭔가 잘못했을 것입니다 ...
객체 계층에서는 계층이 그래프가 아닌 트리 (노드에 하나의 부모가 있음)로 유지해야합니다.
C ++의 Dread Diamond의 실제 문제는 ( 디자인이 사운드라고 가정하고 코드를 검토해야한다고 가정 ) 선택해야한다는 것입니다 .
A
가 레이아웃에 두 번 존재 하는 것이 바람직 합니까? 그것은 무엇을 의미합니까? 그렇다면 반드시 두 번 상속하십시오.이 선택은 문제에 내재되어 있으며 C ++에서는 다른 언어와 달리 언어 수준에서 디자인을 강요하지 않고 실제로 할 수 있습니다.
그러나 모든 힘과 마찬가지로 그 힘도 책임이 있습니다. 설계를 검토하십시오.
위에서 언급 한 Dread of Diamond를 만나지 않기 때문에 0 개 또는 1 개의 구체적인 클래스와 0 개 이상의 인터페이스를 여러 번 상속하는 것이 좋습니다. 사실, 이것은 자바에서 일이 이루어지는 방식입니다.
C의에서 상속 할 때 일반적으로, 당신은 무엇을 의미 A
하고 B
사용자가 사용할 수 있다는 것입니다 C
그것이 인 것처럼 A
, 및 / 또는 것처럼이 있었다 B
.
C ++에서 인터페이스는 다음과 같은 추상 클래스입니다.
하나의 실제 객체에 대한 다중 상속과 0 개 이상의 인터페이스는 "적은"것으로 간주되지 않습니다 (적어도 많지 않음).
첫째, 실제 기준은 상태가 없어야합니다 (즉,을 제외한 멤버 변수가 없음 this
). 귀하의 추상 인터페이스의 요점은 계약을 게시하는 것입니다 ( "당신은 이런 식으로 저에게 전화 할 수 있습니다"). 추상적 가상 방법 만 갖는 한계는 의무가 아니라 디자인 선택이어야합니다.
둘째, C ++에서는 추가 비용 / 간접에도 불구하고 추상 인터페이스에서 사실상 상속하는 것이 합리적입니다. 그렇지 않으면 인터페이스 상속이 계층 구조에서 여러 번 나타나는 경우 모호성이 있습니다.
셋째, 객체 지향은 매우 중요하지만, 그렇지 않은 유일한 진실 아웃이 TM 에서 C ++. 올바른 도구를 사용하고 C ++에서 다른 종류의 솔루션을 제공하는 다른 패러다임을 항상 기억하십시오.
어쩔 땐 그래.
일반적으로, 당신의 C
클래스에서 상속되지 A
및 B
및 A
및 B
(즉,하지 동일한 계층 구조에서 일반적으로, 다른 개념 등의 아무것도)이 관련이없는 개체가 있습니다.
예를 들어, Nodes
X, Y, Z 좌표 시스템을 사용하여 많은 기하학적 계산 (아마도 점, 기하학적 객체의 일부)을 수행 할 수 있으며 각 노드는 다른 에이전트와 통신 할 수있는 자동 에이전트입니다.
아마 당신은 이미 두 라이브러리 자체 네임 스페이스 각각에 액세스 할 수 있습니다 (네임 스페이스를 사용하는 또 다른 이유를 ...하지만 당신은, 당신을 네임 스페이스를하지 사용할 수 있습니까?) 한 존재 geo
와 다른 존재를ai
자신이 그래서 own::Node
에서 모두 파생를 ai::Agent
하고 geo::Point
.
이 때 컴포지션을 대신 사용하지 말아야하는지 스스로에게 물어봐야 할 순간입니다. 경우 own::Node
정말 정말 둘 다 ai::Agent
하고 geo::Point
, 다음 구성은하지 않습니다.
그런 다음 own::Node
3D 공간에서의 위치에 따라 다른 상담원과 의사 소통 할 수있는 다중 상속이 필요 합니다.
(당신은 점에 유의거야 ai::Agent
와 geo::Point
완전히, 완전히, 완전히 관련이없는 ...이 크게 다중 상속의 위험을 감소)
다른 경우가 있습니다 :
this
)때로는 컴포지션을 사용할 수 있으며 때로는 MI가 더 좋습니다. 요점은 다음과 같습니다. 선택의 여지가 있습니다. 책임감있게 코드를 검토하십시오.
대부분의 경우 내 경험으로는 그렇지 않습니다. MI는이 더미에 게으른에 의해 사용될 수 있기 때문에 (A 만들기 같은 결과를 실현하지 않고 함께 기능, 작동하는 것 같다 경우에도 올바른 도구가 아닙니다 Car
둘 모두 Engine
와를 Wheel
).
그러나 때때로 그렇습니다. 그리고 그 당시에는 MI보다 나은 것이 없습니다.
그러나 MI는 냄새가 나기 때문에 코드 검토에서 아키텍처를 방어 할 수 있도록 준비하십시오. 방어 할 수없는 경우 방어하지 않는 것이 좋습니다.
사람들은 다중 상속이 필요하지 않다고 말합니다. 다중 상속으로 할 수있는 것은 단일 상속으로도 할 수 있기 때문입니다. 내가 언급 한 위임 트릭을 사용하면됩니다. 또한 단일 상속으로 수행하는 작업은 클래스를 통해 전달하여 상속하지 않고도 수행 할 수 있으므로 상속이 전혀 필요하지 않습니다. 실제로 포인터와 데이터 구조로 모두 할 수 있기 때문에 클래스가 필요하지 않습니다. 그러나 왜 그렇게 하시겠습니까? 언어 시설을 사용하는 것이 언제 편리합니까? 언제 해결 방법을 선호하십니까? 다중 상속이 유용한 경우를 보았고 복잡한 다중 상속이 유용한 경우도 보았습니다. 일반적으로, 나는 해결책으로 언어가 제공하는 기능을 사용하는 것을 선호합니다
const
- 클래스가 실제로 가변 및 불변 변수를 가질 필요 가있을 때 (보통 인터페이스와 컴포지션을 사용하여) 어리석은 해결 방법을 작성 해야 했습니다. 그러나 다중 상속을 한 번도 놓치지 않았 으며이 기능이 없기 때문에 해결 방법을 작성해야한다고 생각한 적이 없습니다. 그 차이입니다. 모든 경우에 나는 이제까지 본 적이 없는 MI 더 나은 디자인 선택이 아닌 해결 방법입니다 사용.
피할 이유가 없으며 상황에서 매우 유용 할 수 있습니다. 그래도 잠재적 인 문제를 알고 있어야합니다.
가장 큰 것은 죽음의 다이아몬드입니다.
class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;
이제 Child 내에 GrandParent의 "복사본"이 두 개 있습니다.
C ++은 이것을 생각했지만 가상 상속을 통해 문제를 해결할 수 있습니다.
class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;
항상 디자인을 검토하고 상속을 사용하여 데이터 재사용을 절약하지 않도록하십시오. 구성과 같은 것을 표현할 수 있다면 (그리고 일반적으로 할 수있다) 이것은 훨씬 더 나은 접근 방법입니다.
GrandParent
있습니다 Child
. 사람들은 언어 규칙을 이해하지 못할 것이라고 생각하기 때문에 MI에 대한 두려움이 있습니다. 그러나 이러한 간단한 규칙을 얻을 수없는 사람도 사소한 프로그램을 작성할 수 없습니다.
w : 다중 상속을 참조하십시오 .
다중 상속은 비판을 받았으므로 많은 언어로 구현되지 않습니다. 비판에는 다음이 포함됩니다.
- 복잡성 증가
- 시맨틱 모호성은 종종 다이아몬드 문제 로 요약된다 .
- 단일 클래스에서 명시 적으로 여러 번 상속 할 수 없음
- 상속 순서 변경 클래스 의미.
C ++ / Java 스타일 생성자를 사용하는 언어의 다중 상속은 생성자와 상속 체인의 상속 문제를 악화시켜 이러한 언어에서 유지 관리 및 확장 성 문제를 만듭니다. 생성자 체인 패러다임에 따라 매우 다양한 생성 방법을 가진 상속 관계에있는 객체를 구현하기가 어렵습니다.
COM 및 Java 인터페이스와 같은 인터페이스 (순수 추상 클래스)를 사용하도록이를 해결하는 현대적인 방법입니다.
이 대신 다른 일을 할 수 있습니까?
그래 넌 할수있어. 나는 GoF 에서 훔칠 것 입니다.
공공 상속은 IS-A 관계이며 때로는 클래스가 여러 다른 클래스의 유형이 될 수 있으며 때로는 이것을 반영하는 것이 중요합니다.
"Mixins"도 때때로 유용합니다. 그것들은 일반적으로 작은 클래스이며 일반적으로 어떤 것도 상속받지 않으며 유용한 기능을 제공합니다.
상속 계층 구조가 상당히 얕고 (거의 항상 그렇듯이) 잘 관리되는 한, 당신은 끔찍한 다이아몬드 상속을 얻지 못할 것입니다. 다이아몬드는 다중 상속을 사용하는 모든 언어에서 문제가되지 않지만 C ++의 처리는 종종 어색하고 때로는 수수께끼입니다.
다중 상속이 매우 편리한 경우가 있지만 실제로는 거의 없습니다. 다중 상속이 실제로 필요하지 않은 경우 다른 디자인 방법을 선호하기 때문일 수 있습니다. 혼란스러운 언어 구성을 피하고 싶고, 무슨 일이 일어나고 있는지 이해하기 위해 매뉴얼을 잘 읽어야하는 상속 사례를 쉽게 구축 할 수 있습니다.
다중 상속을 "피하지"말아야하지만 '다이아몬드 문제'( http://en.wikipedia.org/wiki/Diamond_problem )와 같이 발생할 수있는 문제를 알고 주의해서 제공하는 힘을 처리해야합니다. 모든 힘을 다해야합니다.
약간 추상적이 될 위험이 있기 때문에 나는 범주 이론의 틀 안에서 상속에 대해 생각하는 것이 밝혀졌다.
상속 관계를 나타내는 모든 클래스와 화살표를 생각하면 다음과 같습니다.
A --> B
class B
에서 파생 된 것을 의미합니다 class A
. 주어진
A --> B, B --> C
우리는 C가 A에서 파생 된 B에서 파생 되었기 때문에 C는 A에서 파생되었다고합니다.
A --> C
또한, 우리 A
는 사소하게 A
파생 된 모든 클래스에 대해 A
상속 모델이 범주의 정의를 충족 한다고 말합니다 . 좀 더 전통적인 언어로, 우리는 Class
모든 클래스와 형태와 상속 관계를 가진 객체를 가진 범주를 가지고 있습니다.
약간의 설정이지만, 다이아몬드 오브 둠을 살펴 보겠습니다.
C --> D
^ ^
| |
A --> B
그늘진 모양의 다이어그램이지만 그렇게 할 것입니다. 그래서 D
모두에서 상속 A
, B
그리고 C
. 또한 OP의 문제 해결에 가까워지면서의 D
모든 수퍼 클래스에서도 상속됩니다 A
. 다이어그램을 그릴 수 있습니다
C --> D --> R
^ ^
| |
A --> B
^
|
Q
이제 문제는 죽음의 다이아몬드와 관련된 여기 때입니다 C
및 B
공유가있는 특성 / 메소드 이름과 일이 모호한 얻을; 그러나 공유 된 동작을 이동 A
하면 모호성이 사라집니다.
범주 측면에서 말하면, 우리가 원하는 A
, B
그리고 C
경우 것이어야 B
하고 C
상속 Q
후가 A
의 서브 클래스로 다시 작성할 수 있습니다 Q
. 이것은 A
무언가를 pushout 이라고합니다 .
풀백D
이라는 대칭 구조도 있습니다 . 이것은 본질적으로 and 에서 상속하는 가장 유용한 유용한 클래스 입니다. 당신이 다른 클래스가있는 경우 즉, 다중 상속 하고 , 다음 클래스 인 의 하위 클래스로 다시 쓸 수는 .B
C
R
B
C
D
R
D
다이아몬드 팁이 풀백 및 푸시 아웃인지 확인하면 이름 충돌 또는 유지 관리 문제를 일반적으로 처리 할 수있는 좋은 방법이됩니다.
참고 Paercebal 의 답변 은 우리가 가능한 모든 클래스의 전체 범주 클래스에서 작업한다는 점을 감안할 때 위의 모델에서 그의 훈계가 암시되기 때문에 이것에 영감을주었습니다.
다중 상속 관계가 강력하고 문제가없는 복잡한 방법을 보여주는 것으로 그의 주장을 일반화하고 싶었습니다.
TL; DR 프로그램의 상속 관계를 범주를 형성하는 것으로 생각하십시오. 그런 다음 상속되는 여러 클래스를 푸시 아웃하고 대칭 적으로 만들어 풀백 인 공통 부모 클래스를 만들어 Diamond of Doom 문제를 피할 수 있습니다.
우리는 에펠을 사용합니다. 우리는 우수한 MI를 가지고 있습니다. 걱정 마. 문제 없습니다. 쉽게 관리 할 수 있습니다. MI를 사용하지 않는 경우가 있습니다. 그러나 사람들이 다음과 같은 이유로 사람들이 알고있는 것보다 유용합니다. 목록에 너무 많으면 확실합니다 (위의 답변 참조).
우리에게 에펠 탑을 사용하는 MI는 도구 상자의 다른 도구와 마찬가지로 다른 도구와 마찬가지로 자연 스럽습니다. 솔직히, 우리는 다른 사람이 에펠을 사용하고 있지 않다는 사실에 대해 크게 걱정하지 않습니다. 걱정 마. 우리는 우리가 가진 것에 만족하고 당신을 한 번 보도록 초대합니다.
보고있는 동안 : Void-safety와 Null 포인터 역 참조 근절에주의하십시오. 우리 모두 MI 주변에서 춤을 추는 동안 포인터가 사라집니다! :-)
모든 프로그래밍 언어에는 장단점이있는 객체 지향 프로그래밍 처리 방식이 약간 다릅니다. C ++ 버전은 성능에 중점을두고 무효 코드를 작성하는 것이 혼란스럽게 쉽다는 단점이 있습니다. 이는 다중 상속에 해당됩니다. 결과적으로 프로그래머를이 기능에서 멀어지게하는 경향이 있습니다.
다른 사람들은 다중 상속이 좋지 않은 것에 대한 문제를 해결했습니다. 그러나 우리는 그것을 피해야하는 이유가 안전하지 않기 때문이라는 것을 암시하는 많은 의견을 보았습니다. 예, 아니오
C ++에서 종종 그렇듯이 기본 지침을 따르면 "어깨를 쳐다 보지 않아도"안전하게 사용할 수 있습니다. 핵심 아이디어는 "mix-in"이라는 특별한 종류의 클래스 정의를 구별하는 것입니다. 모든 멤버 함수가 가상 (또는 순수 가상) 인 경우 클래스는 믹스 인입니다. 그런 다음 단일 메인 클래스와 원하는만큼의 "mix-in"을 상속받을 수 있지만 "virtual"이라는 키워드로 mixin을 상속해야합니다. 예 :
class CounterMixin {
int count;
public:
CounterMixin() : count( 0 ) {}
virtual ~CounterMixin() {}
virtual void increment() { count += 1; }
virtual int getCount() { return count; }
};
class Foo : public Bar, virtual public CounterMixin { ..... };
내 제안은 클래스를 믹스 인 클래스로 사용하려는 경우 코드를 읽는 사람이 무슨 일이 일어나고 있는지 확인하고 기본 지침의 규칙에 따라 연주하고 있는지 확인하기 위해 명명 규칙을 채택한다는 것입니다 . 그리고 가상 기본 클래스가 작동하는 방식 때문에 믹스 인에 기본 생성자가있는 경우 훨씬 더 효과적입니다. 또한 모든 소멸자를 가상으로 만들어야합니다.
여기서 "mix-in"이라는 단어를 사용하는 것은 매개 변수가있는 템플릿 클래스와 같지 않지만 ( 이 설명을 잘 보려면 이 링크 를 참조하십시오) 이 용어를 공정하게 사용한다고 생각합니다.
이제 이것이 다중 상속을 안전하게 사용하는 유일한 방법이라는 인상을 남기고 싶지 않습니다. 확인하기 쉬운 방법 중 하나입니다.
신중하게 사용해야합니다. 다이아몬드 문제 와 같은 상황이 복잡해질 수 있습니다.
(출처 : learncpp.com )
Printer
조차도해서는 안됩니다 PoweredDevice
. A Printer
는 전원 관리가 아니라 인쇄용입니다. 특정 프린터를 구현하려면 일부 전원 관리가 필요할 수 있지만 이러한 전원 관리 명령은 프린터 사용자에게 직접 노출되어서는 안됩니다. 이 계층 구조의 실제 사용을 상상할 수 없습니다.
이 기사는 상속을 설명하는 데 큰 도움이되며 위험합니다.
다이아몬드 패턴 외에도 다중 상속은 객체 모델을 이해하기 어렵게 만들고 유지 보수 비용을 증가시킵니다.
작곡은 본질적으로 이해하고 이해하고 설명하기가 쉽습니다. 코드를 작성하는 것은 지루할 수 있지만 좋은 IDE (Visual Studio로 작업한지 몇 년이 지났지 만 Java IDE에는 모두 훌륭한 구성 단축키 자동화 도구가 있습니다)는 그 장애물을 극복해야합니다.
또한 유지 관리 측면에서 비 마이너스 상속 인스턴스에서도 "다이아몬드 문제"가 발생합니다. 예를 들어 A와 B가 있고 클래스 C가 두 가지를 모두 확장하고 A에 오렌지 주스를 만드는 'makeJuice'방법이 있고 라임을 꼬아 서 오렌지 주스를 만들기 위해이를 확장하면 디자이너가 ' B '는 전류를 생성하고 생성하는'makeJuice '방법을 추가합니까? 'A'와 'B'는 현재 호환 가능한 "부모" 일 수 있지만 이것이 항상 그렇게된다는 것을 의미하지는 않습니다!
전반적으로 상속, 특히 다중 상속을 피하려는 경향이 최대입니다. 최대 값으로 예외가 있지만 코딩하는 예외를 가리키는 녹색 네온 사인이 깜박이는지 확인해야합니다. 그런 상속 트리를 볼 때마다 자신의 녹색 네온 네온에서 그릴 수 있도록 뇌를 훈련 시키십시오. sign), 그리고 당신은 모든 것이 때때로 의미가 있는지 확인합니다.
what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?
어, 물론 컴파일 오류가 발생합니다 (사용이 모호한 경우).
콘크리트 물체의 MI와 관련된 주요 문제는 합법적으로 "A가되고 B가되어야"하는 물체가 거의 없기 때문에 논리적으로 올바른 해결책은 아닙니다. 훨씬 더 자주, 당신은 "C는 A 또는 B의 역할을 할 수 있습니다"라는 객체 C를 가지고 있는데, 이것은 인터페이스 상속과 구성을 통해 달성 할 수 있습니다. 그러나 여러 인터페이스의 실수 상속은 여전히 MI이며 그중 일부일뿐입니다.
특히 C ++의 경우 기능의 주요 약점은 다중 상속의 실제 존재가 아니지만 거의 항상 변형되는 일부 구조입니다. 예를 들어 다음과 같은 동일한 객체의 여러 복사본을 상속합니다.
class B : public A, public A {};
DEFINITION의 형식이 잘못되었습니다. 영어로 번역하면 "B는 A와 A"입니다. 따라서 인간의 언어로도 모호한 점이 있습니다. "B에 2가 있습니다"또는 "B가 A입니다"라는 의미입니까? 그러한 병리학 적 코드를 허용하고 사용 사례를 악화시키는 것은 C ++이 기능을 후속 언어로 유지하는 경우에 유리하지 않았습니다.
상속보다 구성을 우선적으로 사용할 수 있습니다.
일반적인 느낌은 구성이 더 우수하다는 것입니다.
The general feeling is that composition is better, and it's very well discussed.
구성이 더 좋다는 의미는 아닙니다.
관련된 클래스 당 4/8 바이트가 필요합니다. (클래스 당이 포인터 하나).
이것은 결코 걱정할 수 없지만 언젠가는 수십억 시간에 이르는 마이크로 데이터 구조가 있다면 그렇게 될 것입니다.