개인 순수 가상 기능의 요점은 무엇입니까?


139

헤더 파일에서 다음 코드를 발견했습니다.

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

나에게 이것은 Engine클래스 또는 클래스에서 파생 된 클래스가 순수한 가상 함수에 대한 구현을 제공해야 함을 의미 합니다. 그러나 파생 클래스가 클래스를 다시 구현하기 위해 개인 함수에 액세스 할 수 있다고 생각하지 못했습니다. 왜 가상으로 만들까요?

답변:


209

이 주제의 질문은 매우 일반적인 혼란을 시사합니다. 혼란은 충분히 흔합니다. C ++ FAQ 나쁜 것으로 보였기 때문에 개인용 가상 사용을 오랫동안 옹호 입니다.

따라서 혼동을 먼저 제거하려면 : 예, 파생 클래스에서 개인용 가상 기능을 무시할 수 있습니다. 파생 클래스의 메서드는 기본 클래스에서 가상 함수를 호출 할 수 없지만 자체 구현을 제공 할 수 있습니다. Herb Sutter에 따르면 기본 클래스에 퍼블릭 비가 상 인터페이스와 파생 클래스에서 커스터마이즈 할 수있는 전용 구현을 통해 "구현 가능한 사용자 정의 동작의 사양에서 인터페이스 사양을 분리"할 수 있다고합니다. 그의 기사 "Virtuality" 에서 자세한 내용을 읽을 수 있습니다 .

그러나 당신이 제시 한 코드에는 흥미로운 점이 하나 더 있습니다. 공용 인터페이스는 오버로드 된 비가 상 함수 세트로 구성되며 해당 함수는 퍼블릭이 아닌 오버로드 된 가상 함수를 호출합니다. C ++ 세계에서와 마찬가지로이 용어는 관용구이며 이름이 있으며 물론 유용합니다. 이름은 (놀랍고 놀랍습니다!)

"퍼블릭 오버로드 된 비가 상 통화로 보호되는 오버로드되지 않은 가상"

은닉 규칙올바르게 관리하는 데 도움이됩니다 . 여기 에서 자세한 내용을 읽을 수 있지만 잠시 설명하겠습니다.

Engine클래스 의 가상 함수 도 인터페이스이며 순수한 가상이 아닌 오버로드 된 함수 세트라고 상상해보십시오 . 순수한 가상이라면 아래 설명과 같이 여전히 같은 문제가 발생할 수 있지만 클래스 계층 구조는 더 낮습니다.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

이제 파생 클래스를 만들고 싶다고 가정하고 두 개의 정수를 인수로 사용하는 메소드에 대해서만 새로운 구현을 제공해야한다고 가정 해 봅시다.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

파생 클래스에 using 선언을 넣지 않았거나 두 번째 오버로드를 재정의하는 것을 잊어 버린 경우 아래 시나리오에서 문제가 발생할 수 있습니다.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Engine회원 숨기기를 막지 않았다면 다음 과 같이 진술하십시오.

myV8->SetState(5, true);

void SetState( int var, int val )파생 클래스에서 호출 하여로 변환 true합니다 int.

인터페이스가 가상이 아니고 가상 구현이 공개되지 않은 경우 (예 : 파생 클래스의 경우) 파생 클래스의 작성자는 생각해 볼 문제가 적고 간단히 작성할 수 있습니다.

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

가상 기능이 개인용이어야하는 이유는 무엇입니까? 공개 할 수 있습니까?
Rich

"Virtuality"기사에서 Herb Sutter가 제공 한 지침이 여전히 오늘날에도 적용되는지 궁금합니다.
nurabha

@Rich 당신은 할 수 있지만 공개하지 않음으로써 그들의 의도를보다 명확하게 전달할 수 있습니다. 첫째, 인터페이스를 공개하고 구현을 공개하지 않으려는 경우 분리 문제를 보여줍니다. 둘째, 상속 클래스가 기본 구현을 호출 할 수있게하려면 보호 된 것으로 선언 할 수 있습니다. 기본 구현을 호출하지 않고 자체 구현을 제공하기를 원하는 경우 비공개로 만듭니다.
Dan

43

개인 순수 가상 기능은 비가 상 인터페이스 관용구 의 기본입니다 (OK, 항상 순수 가상은 아니지만 여전히 가상입니다). 물론 이것은 다른 것들에도 사용되지만 가장 유용합니다 (: 두 단어 : 공개 함수에서는 처음에 로깅, 통계 등과 같은 일반적인 것들을 넣을 수 있음) 함수의 끝에서 "가운데"를 사용하여이 전용 가상 함수를 호출하면 특정 파생 클래스에 따라 다릅니다.

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

순수한 가상 -파생 클래스를 구현해야합니다.

편집 : 이것에 대한 자세한 내용 : Wikipedia :: NVI-idiom


17

우선, 파생 클래스가 기본 클래스 (순수 가상 함수 선언 포함)가 호출 할 수있는 함수를 구현할 수 있습니다.


5
있음을 단지 기본 클래스를 호출 할 수 있습니다!
underscore_d

4

편집 : 재정의 기능 및 액세스 / 호출 기능에 대한 설명이 명확 해졌습니다.

이러한 개인 기능을 무시할 수 있습니다. 예를 들어, 다음과 같이 고안된 예제가 작동합니다 ( EDIT : 파생 클래스 메서드를 비공개로 만들고 파생 클래스 메서드 호출을 삭제하여 main()사용중인 디자인 패턴의 의도를보다 잘 보여줍니다 ).

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtual코드에있는 것과 같은 기본 클래스의 메서드는 일반적으로 템플릿 메서드 디자인 패턴 을 구현하는 데 사용됩니다 . 이 디자인 패턴을 사용하면 기본 클래스의 코드를 변경하지 않고도 기본 클래스의 알고리즘 동작을 변경할 수 있습니다. 기본 클래스 메소드가 기본 클래스 포인터를 통해 호출되는 위의 코드는 템플리트 메소드 패턴의 간단한 예입니다.


알다시피, 파생 클래스가 어쨌든 일종의 액세스 권한을 가지고 있다면 왜 클래스를 비공개로 만드는 것을 귀찮게합니까?
BeeBand

@BeeBand : 사용자는 공개 파생 클래스 가상 메서드 재정의에 액세스 할 수 있지만 기본 클래스에 액세스 할 수는 없습니다. 이 경우 파생 클래스 작성자는 가상 메서드 재정의를 비공개로 유지할 수 있습니다. 사실 위의 샘플 코드를 변경하여 강조하겠습니다. 어느 쪽이든, 그들은 항상 공개적으로 상속하고 개인 기본 클래스 가상 메소드를 무시할 수 있지만 여전히 자신의 파생 클래스 가상 메소드에만 액세스 할 수 있습니다. 재정의와 액세스 / 호출 사이를 구별하고 있습니다.
Void

당신이 틀 렸기 때문에 클래스 간의 상속 가시성 Engine및 재정의 할 수 없거나 무시할 수없는 DerivedEngine것과 관련이 없습니다 DerivedEngine(또는 그 문제에 대한 액세스).
wilhelmtell

@wilhelmtell : sigh 당신은 물론 정확합니다. 이에 따라 답변을 업데이트하겠습니다.
Void

3

전용 가상 메서드는 지정된 함수를 재정의 할 수있는 파생 클래스 수를 제한하는 데 사용됩니다. 전용 가상 메서드를 재정의해야하는 파생 클래스는 기본 클래스의 친구 여야합니다.

DevX.com에 대한 간단한 설명이 있습니다 .


편집 개인 가상 방법은 템플릿 방법 패턴 에서 효과적으로 사용됩니다 . 파생 클래스는 전용 가상 메서드를 재정의 할 수 있지만 파생 클래스는 기본 클래스 전용 가상 메서드 (예 : SetStateBoolSetStateInt)를 호출 할 수 없습니다 . 기본 클래스 만 프라이빗 가상 메소드를 효과적으로 호출 할 수 있습니다 ( 파생 클래스가 가상 함수의 기본 구현을 호출해야하는 경우에만 가상 함수를 protected로 설정하십시오 ).

Virtuality에 대한 흥미로운 기사를 찾을 수 있습니다 .


2
@ Gentleman ... hmmm Colin D Bennett의 의견까지 아래로 스크롤하십시오. 그는 "비공개 가상 함수는 파생 클래스로 재정의 할 수 있지만 기본 클래스 내에서만 호출 할 수있다"고 생각합니다. @Michael Goldshteyn도 그렇게 생각합니다.
BeeBand

파생 클래스에서 개인 기반 클래스를 볼 수 없다는 원칙을 잊어 버린 것 같습니다. 이것이 OOP 규칙이며 OOP 인 모든 언어에 적용됩니다. 파생 클래스가 기본 클래스 전용 가상 메서드를 구현 friend하려면 기본 클래스 여야합니다 . Qt는 XML DOM 문서 모델을 구현할 때 동일한 접근 방식을 취했습니다.
Buhake Sindi

@ 젠틀맨 : 아니요, 잊지 않았습니다. 내 의견에 오타를 만들었습니다. "기본 클래스 메소드에 액세스"대신 "기본 클래스 메소드를 대체 할 수 있습니다"라고 작성해야합니다. 파생 클래스는 해당 기본 클래스 메소드에 액세스 할 수없는 경우에도 개인용 가상 기본 클래스 메소드를 대체 할 수 있습니다. 지적한 DevX.com 기사가 잘못되었습니다 (공개 상속). 내 답변에서 코드를 사용해보십시오. 개인용 가상 기본 클래스 방법에도 불구하고 파생 클래스는이를 재정의 할 수 있습니다. 전용 가상 기본 클래스 메서드를 재정의하는 기능과 메서드를 호출하는 기능을 혼동하지 마십시오.
Void

@ Gentleman : @wilhelmtell은 내 답변 / 의견에 오류가 있음을 지적했습니다. 기본 클래스 메서드의 파생 클래스 액세스 가능성에 영향을 미치는 상속에 대한 주장은 벗어났습니다. 귀하의 답변에 대한 위반 의견을 삭제했습니다.
Void

@Void, 파생 클래스 기본 클래스 개인 가상 메서드를 재정의 할 수는 있지만 사용할 수는 없습니다. 따라서 이것은 기본적으로 템플릿 메소드 패턴입니다.
Buhake Sindi

0

TL; DR 답변 :

보호개인 사이의 다른 캡슐화 수준처럼 처리 할 수 ​​있습니다 . 자식 클래스에서 호출 할 수는 없지만 재정의 할 수는 있습니다.

템플릿 메소드 디자인 패턴을 구현할 때 유용합니다 . protected를 사용할 수 있지만 더 나은 캡슐화로 인해 virtual 과 함께 private이 더 나은 선택으로 간주 될 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.