C ++의 프라이빗 가상 메서드


125

C ++에서 private 메서드를 가상으로 만드는 이점은 무엇입니까?

오픈 소스 C ++ 프로젝트에서 이것을 발견했습니다.

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
질문은 거꾸로 생각합니다. 가상으로 만드는 이유는 항상 동일합니다. 파생 클래스가이를 재정의 할 수 있기 때문입니다. 따라서 질문은 다음과 같습니다. 가상 메서드를 비공개로 만드는 이점은 무엇입니까? 대답은 기본적으로 모든 것을 비공개로 설정하는 것입니다. :-)
ShreevatsaR 2014

1
@ShreevatsaR 그러나 당신은 ...... 당신의 자신의 질문에 대답하지 않았다
스펜서

@ShreevatsaR 나는 당신이 다른 방식으로 거꾸로 의미한다고 생각했습니다 : 가상 방법 비공개가 아닌 것으로 만드는 이점은 무엇입니까 ?
피터 - 분석 재개 모니카

답변:


116

Herb Sutter는 여기에 그것을 아주 잘 설명했습니다 .

지침 # 2 : 가상 기능을 비공개로 설정하는 것을 선호합니다.

이렇게하면 파생 클래스가 함수를 재정 의하여 필요에 따라 동작을 사용자 지정하고 파생 클래스에서 가상 함수를 직접 호출 할 수있게함으로써 (함수가 방금 보호 된 경우 가능했던 것처럼) 직접 노출하지 않아도됩니다. 요점은 사용자 정의가 가능한 가상 기능이 존재한다는 것입니다. 파생 클래스의 코드 내에서 직접 호출 할 필요가없는 경우에는 개인용으로 만 만들 필요가 없습니다.


내 대답에서 짐작할 수 있듯이 Sutter의 지침 # 3은 지침 # 2를 창 밖으로 밀어내는 것 같습니다.
Spencer

66

메서드가 가상 메서드 인 경우 전용 클래스 인 경우에도 파생 클래스에서 재정의 할 수 있습니다. 가상 메서드가 호출되면 재정의 된 버전이 호출됩니다.

(Prasoon Saurav가 그의 답변에서 인용 한 Herb Sutter와는 달리, C ++ FAQ Lite 대부분 사람들을 혼란스럽게하기 때문에 개인용 가상 장치에 대해 권장 합니다.)


41
C ++ FAQ Lite는 그 이후 권장 사항을 변경 한 것으로 보입니다. " C ++ FAQ는 이전에 개인 가상이 아닌 보호 된 가상을 사용하도록 권장했습니다. 그러나 이제 개인 가상 접근 방식은 초보자의 혼동을 덜 우려 할 정도로 일반적입니다. "
Zack The 인간

19
그러나 전문가들의 혼란은 여전히 ​​우려 사항입니다. 내 옆에 앉은 4 명의 C ++ 전문가 중 개인 가상에 대해 아는 사람은 없었습니다.
Newtonx

12

가상 멤버를 비공개로 선언하라는 모든 호출에도 불구하고이 주장은 단순히 물을 유지하지 않습니다. 종종 파생 클래스의 가상 함수 재정의는 기본 클래스 버전을 호출해야합니다. 선언되면 할 수 없습니다 private.

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

당신은 기본 클래스 메서드를 선언 protected.

그런 다음 메서드를 재정의해야하지만 호출해서는 안된다는 주석을 통해 표시하는 추악한 방법을 취해야합니다.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

따라서 Herb Sutter의 가이드 라인 # 3 ... 그러나 말은 어쨌든 헛간에서 나왔습니다.

무언가를 선언 할 때 protected어떤 파생 클래스의 작성자가 보호 된 내부를 이해하고 적절하게 사용하기 위해 암시 적으로 신뢰하는 것입니다. friend선언이 private멤버에 대한 더 깊은 신뢰를 의미하는 것과 같습니다 .

그러한 신뢰를 위반하여 나쁜 행동을하는 사용자 (예 : 문서를 읽지 않고 '단서 없음'으로 표시)는 자신을 비난 할 수 있습니다.

업데이트 : 비공개 가상 기능을 사용하여 가상 기능 구현을 이러한 방식으로 "체인"할 수 있다는 의견이 있습니다. 그렇다면 꼭보고 싶습니다.

필자가 사용하는 C ++ 컴파일러는 파생 클래스 구현이 전용 기본 클래스 구현을 호출하도록 허용하지 않습니다.

C ++위원회가이 특정 액세스를 허용하기 위해 "비공개"를 완화했다면, 저는 모두 비공개 가상 기능을 사용할 것입니다. 말이 도난당한 후에도 헛간 문을 잠 그라는 권고를 받고 있습니다.


3
나는 당신의 주장이 유효하지 않다고 생각합니다. API 개발자는 잘못 사용하기 어려운 인터페이스를 위해 노력해야하며 그렇게 할 때 다른 개발자를 설정하지 않아야합니다. 예제에서 수행하려는 작업은 전용 가상 메서드로만 구현할 수 있습니다.
sigy

1
내가 말한 게 아니에요. 그러나 개인 기본 클래스 함수를 호출 할 필요없이 동일한 효과를 얻기 위해 코드를 재구성 할 수 있습니다
sigy

3
귀하의 예에서 set_data. 지침 m_data = ndata;과는 cleanup();따라서 모든 구현을 위해 보유해야 불변 간주 될 수 있습니다. 따라서 cleanup()비가 상 및 비공개로 만드십시오 . 가상 인 다른 개인 메서드와 클래스의 확장 지점에 대한 호출을 추가합니다. 이제 파생 클래스가 cleanup()더 이상 base를 호출 할 필요가 없으며 코드가 깨끗하게 유지되고 인터페이스를 잘못 사용하기가 어렵습니다.
sigy

2
@sigy 그것은 골대를 이동합니다. 당신은 단순한 예를 넘어서 볼 필요가 있습니다. cleanup()체인의 모든 s 를 호출해야하는 추가 하위 항목이 있으면 인수가 분리됩니다. 아니면 체인의 각 하위 항목에 대해 추가 가상 기능을 권장합니까? Ick. Herb Sutter조차도 그의 가이드 라인 # 3에서 보호 된 가상 기능을 허점으로 허용했습니다. 어쨌든 실제 코드가 없으면 절대 납득할 수 없습니다.
Spencer

2
그렇다면 동의하지 않는 것에 동의합시다;)
sigy

9

Scott Meyers의 'Effective C ++', Item 35 : 가상 기능의 대안 고려 를 읽으면서이 개념을 처음 접했습니다 . 관심을 가질만한 다른 사람들을 위해 Scott Mayers를 참조하고 싶었습니다.

Non-Virtual Interface 관용구를 통한 템플릿 메소드 패턴 의 일부입니다. . 공개 된 메소드는 가상이 아닙니다. 오히려 그들은 비공개 인 가상 메서드 호출을 래핑합니다. 그런 다음 기본 클래스는 비공개 가상 함수 호출 전후에 논리를 실행할 수 있습니다.

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

이것은 매우 흥미로운 디자인 패턴이라고 생각하며 추가 된 컨트롤이 어떻게 유용한 지 알 수있을 것입니다.

  • 가상 기능을 만드는 이유 private 입니까? 가장 좋은 이유는 우리가 이미 public대면 방법을 제공했기 때문입니다 .
  • 단순히 만들지 않는 이유 protected다른 흥미로운 일에도이 방법을 사용할 수 있도록 것이 어떻습니까? 나는 그것이 항상 당신의 디자인과 당신이 기본 클래스가 어떻게 맞다고 믿는지에 달려 있다고 생각한다. 나는 파생 된 클래스 제작자가 필요한 논리를 구현하는 데 집중해야한다고 주장한다. 다른 모든 것은 이미 처리되었습니다. 또한 캡슐화 문제가 있습니다.

C ++ 관점에서 볼 때 클래스에서 호출 할 수 없더라도 private 가상 메서드를 재정의하는 것은 완전히 합법적입니다. 이것은 위에서 설명한 디자인을 지원합니다.


3

필자는 파생 클래스가 최종 사용자에게 그러한 구멍을 노출하지 않고 기본 클래스에 대해 "공백을 채울"수 있도록 허용하는 데 사용합니다. 예를 들어, 전체 상태 시스템의 2/3 만 구현할 수있는 공통 기반에서 파생 된 고도로 상태 저장 개체가 있습니다 (파생 클래스는 템플릿 인수에 따라 나머지 1/3을 제공하며 기본은 템플릿이 될 수 없습니다. 다른 이유들).

많은 공용 API가 올바르게 작동하도록하려면 공통 기본 클래스가 필요하지만 (가변 템플릿을 사용하고 있습니다) 해당 개체를 야생으로 내보낼 수는 없습니다. 더 나쁜 것은 내가 크레이터를 순수한 가상 함수의 형태로 상태 머신에 남겨두면 "Private"가 아닌 모든 곳에있는 경우, 하위 클래스 중 하나에서 파생 된 영리하거나 우둔한 사용자가 사용자가 절대 만져서는 안되는 메서드를 재정의 할 수 있습니다. 그래서 상태 머신 '브레인'을 PRIVATE 가상 기능에 넣었습니다. 그런 다음 기본 클래스의 직계 자식이 가상이 아닌 재정의에 대한 공백을 채우고 사용자는 상태 시스템을 망칠 염려없이 결과 개체를 안전하게 사용하거나 추가 파생 클래스를 만들 수 있습니다.

공개 가상 메소드를 가져서는 안된다는 주장에 대해서는 BS라고합니다. 사용자는 공개 가상 가상 시스템처럼 쉽게 개인 가상 가상을 부적절하게 재정의 할 수 있습니다. 결국 새 클래스를 정의하는 것입니다. 대중이 특정 API를 수정해서는 안된다면 공개적으로 액세스 할 수있는 객체에서 가상으로 만들지 마십시오.

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