5

필자는 현재 가상 프로젝트 (인터페이스)를 사용하여 현재 프로젝트의 여러 클래스 구현 간의 종속성을 줄입니다. 순수한 가상 클래스와 비 순수 가상 클래스가 다른 순수 가상 클래스를 확장하는 계층 구조를 갖는 것도 드문 일이 아닙니다. 다음은 이러한 상황의 예는 다음과 같습니다순수 가상 (인터페이스) 클래스를 가상 상속하는 것이 좋은 방법입니까?

class Engine 
{ /* Declares pure virtual methods only */ } 

class RunnableEngine : public virtual Engine 
{ /* Defines some of the methods declared in Engine */ } 

class RenderingEngine : public virtual Engine 
{ /* Declares additional pure virtual methods only */ } 

class SimpleOpenGLRenderingEngine : public RunnableEngine, 
    public virtual RenderingEngine 
{ /* Defines the methods declared in Engine and RenderingEngine (that are not 
    already taken care of by RunnableEngine) */ } 

모두 RunnableEngineRenderingEngine 사실상 다이아몬드 문제가 SimpleOpenGLRenderingEngine에 영향을 미치지 않도록 Engine을 확장합니다.

나는 문제가 될 때 다이아몬드 문제를 예방하는 입장을 취하고 싶습니다. 특히 다른 사람이 사용하기 쉬운 코드를 쓰고 싶습니다. 원하지 않습니다. 특정 클래스 계층을 생성 할 수 있도록 클래스를 수정해야합니다. 밥이 작업을 수행하기를 원한다면 : 나는 SimpleOpenGLRenderingEngine 사실상 RenderingEngine을 확장하게하지 않았다면

class BobsRenderingEngine : public virtual RenderingEngine 
{ /* Declares additional pure virtual methods only */ } 

class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine, 
    public BobsRenderingEngine 
{ /* Defines the methods declared in BobsRenderingEngine */ } 

이 가능하지 않을 것입니다. 나는 Bob이 이것을 원할 확률이 매우 낮을 수 있음을 알고 있습니다.

그렇기 때문에 순수 가상 클래스를 사실상 확장하여 규칙 상 다중 상속으로 인해 다이아몬드 문제가 발생하지 않도록하기 시작했습니다. 어쩌면 이것은 Java에서 왔고 순수하지 않은 가상 클래스로 단일 상속만을 사용하려는 경향이 있기 때문일 수 있습니다. 어떤 상황에서는 과잉이라고 나는 확신하지만,이 대회를 사용하는 데있어서 단점이 있습니까? 이로 인해 성능/기능 등의 문제가 발생할 수 있습니까? 그렇지 않다면, 종종 마지막에 필요하지 않을지라도, 대회를 사용하지 않을 이유는 없습니다.

+3

그래, 완전한 과용. 선택할 수있는 여러 가지 구현 *이있을 때만 실제 다이아몬드 문제입니다. 인터페이스의 문제는 아니지만 구현이 없습니다. 가상 상속은 반창고이며, 등을 벽에 대어 사용할 때만 사용합니다. –

+2

@HansPassant, 나는 당신에게 동의하지 않습니다. 다이 몬드 문제는 순수 가상 클래스에서도 종종 발생할 수 있습니다. IA와 그 기본 구현 인'AImpl : public virtual IA'라는 인터페이스가 있다고 가정하면 가상 상속을 사용하여'class B : public virtual IA, public AImpl'을 정의 할 수 있습니다. 따라서 B는 AImpl의 IA 구현을 사용합니다. –

+0

@kids_fok 그래, 그게 내가 문제가있는 곳이야. 자, 예제에서 명시 적으로'IA '를 확장하기 위해'B'가 필요하지는 않습니다. 제 질문의 예제는 비슷합니다. –

답변

2

일반적으로 상속을 권장하는 것은 좋지 않은 아이디어입니다. 가상 상속을 사용하면이 아이디어를 얻을 수 있습니다. 실제로 상속에 의해 암시되는 나무 구조로 표현되는 것은 거의 없습니다. 또한 다중 상속은 단일 책임 규칙을 어기는 경향이 있음을 알게되었습니다. 귀하의 정확한 사용 사례를 알지 못해서 때때로 다른 선택의 여지가 없음에 동의합니다.

그러나 C++에서 우리는 캡슐화 객체를 구성하는 또 다른 방법이있다. 나는 아래의 선언이 훨씬 더 쉽게 이해하고 조작하는 것을 발견 : 개체가 가상 상속보다 더 많은 메모리를 사용하는 것이 사실이다

class SimpleOpenGLRenderingEngine 
{ 
public: 
    RunnableEngine& RunEngine() { return _runner; } 
    RenderingEngine& Renderer() { return _renderer; } 

    operator RunnableEngine&() { return _runner; } 
    operator RenderingEngine&() { return _renderer; } 

private: 
    RunnableEngine _runner; 
    RenderingEngine _renderer; 
}; 

,하지만 난 단지는 엄청난 숫자에서 생성되는 개체를 의심한다.

이제 당신이 정말로 어떤 외부 제약 조건, 상속하고 싶은 말은하자. 가상 상속은 여전히 ​​조작하기가 어렵습니다. 아마도 익숙 할 것입니다. 그러나 클래스에서 파생 된 사람들은 그렇지 않을 수도 있으며, 파생되기 전에 생각하지 않을 수도 있습니다. 개인적인 상속을 사용하는 것이 더 나은 선택 일 것입니다.

class RunnableEngine : private Engine 
{ 
    Engine& GetEngine() { return *this; } 
    operator Engine&() { return *this; } 
}; 

// Similar implementation for RenderingEngine 

class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine 
{ }; 
2

짧은 대답 : 예. 니가 끝냈어.

긴 답 : 적절한 용어는 추상적 클래스가 아닌 "순수 가상 클래스이다 : 그래서

, 나는 항상 확장 순수 가상 클래스

주의의 규칙을 사용하기 시작했다 ".

나는 항상 [abstract] 클래스를 사실상 확장하여 규칙 상 여러 상속이 다이아몬드 문제를 일으키지 않도록하기 시작했습니다.

실제로. 이것은 추상적 인 클래스를위한 건전한 규칙이지만, 인터페이스를 나타내는 모든 클래스의 경우 이며, 그 중 일부는 기본 구현이있는 가상 함수가 있습니다.

(자바 힘의 단점 만 순수 가상 함수 및 "인터페이스"에는 데이터 멤버를 넣어은, C++은 평소와 같이, 선을 위해, 더 유연합니다.)

수있는이 원인이 어떤 문제 성능

가상의 비용에는 비용이 있습니다.

코드를 프로파일하십시오.

[] 기능 등의 문제가있을 수 있습니까?

아마도 (드물게).

기본 클래스를 가상화 할 수 없습니다. 특정 파생 클래스가 두 개의 별개 기본 클래스 하위 개체를 가져야하는 경우 두 분기에 상속을 사용할 수 없으므로 포함이 필요합니다.

1

글쎄, 꽤 간단합니다. 귀하는 단일 책임 원칙 (Single Responsibility Principle, SPR)을 위반했으며 이는 귀하의 문제의 원인입니다. 당신의 "엔진"클래스는 하나님 클래스입니다. 잘 설계된 계층 구조는이 문제점을 호출하지 않습니다.

관련 문제