구현 된 순수 가상 기능


176

내 기본 이해는 순수한 가상 기능에 대한 구현이 없다는 것입니다. 그러나 순수한 가상 기능에 대한 구현이있을 수 있다고 들었습니다.

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

위의 코드가 정상입니까?

구현으로 순수한 가상 기능을 만드는 목적은 무엇입니까?

답변:


215

순수 virtual함수는 직접 인스턴스화 할 파생 유형으로 구현해야하지만 기본 유형은 여전히 ​​구현을 정의 할 수 있습니다. (호출하여 완벽한 범위의 이름을 사용하여 (액세스 권한을 허용하는 경우) 파생 클래스는 명시 적으로 기본 클래스 구현을 호출 할 수 있습니다 A::f()- 경우의 예에서 A::f()있었다 public또는 protected). 다음과 같은 것 :

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

내가 생각할 수있는 유스 케이스는 다소 합리적인 기본 동작이있을 때이지만 클래스 디자이너는 기본 정렬 동작이 명시 적으로 만 호출되기를 원합니다. 파생 클래스가 항상 자체 작업을 수행하지만 공통된 기능 집합을 호출 할 수도 있습니다.

언어에 의해 허용되지만, 내가 일반적으로 사용하는 것으로 보이는 것은 아닙니다 (그리고 그것을 할 수 있다는 사실은 대부분의 C ++ 프로그래머, 심지어 경험이 많은 프로그래머를 놀라게하는 것처럼 보입니다).


1
프로그래머에게 놀라운 이유를 추가하는 것을 잊었습니다. 인라인 정의가 표준에 의해 금지되어 있기 때문입니다. 순수 가상 메소드 정의는이어야합니다 deported. (.inl 또는 .cpp에서 일반적인 파일 이름 지정 방법을 나타냅니다).
— v.oddou

이 호출 메소드는 정적 메소드 멤버 호출과 동일합니다. Java의 일종의 클래스 메소드.
— Sany Liew

2
"일반적으로 사용되지 않음"== 나쁜 습관? 나는 NVI를 구현하려고 시도하는 것과 똑같은 행동을 찾고있었습니다. 그리고 NVI는 저에게 좋은 습관 인 것 같습니다.
— Saskia

5
A :: f ()를 순수하게 만드는 것은 B 가 f ()를 구현 해야 한다는 것을 의미합니다 (그렇지 않으면 B는 추상적이고 인스턴스화 할 수 없습니다). @MichaelBurr가 지적했듯이 A :: f ()에 대한 구현을 제공한다는 것은 B가이를 사용하여 f ()를 정의 할 수 있음을 의미합니다 .
— fearless_fool

2
IIRC, Scot Meyer는 그의 고전 저서 "더 효과적인 C ++"중 하나에서이 질문의 사용 사례에 대한 훌륭한 기사를 가지고 있습니다
— irsis

75

분명히, 당신은 무엇을 오해하고 있습니다 = 0; 가상 기능 후 의미합니다.

= 0은 파생 클래스가 구현을 제공해야한다는 것을 의미하며 기본 클래스가 구현을 제공 할 수는 없습니다.

실제로 가상 함수를 순수 (= 0)로 표시하면 누군가가 Base :: Function (...)을 통해 명시 적으로 수행하지 않는 한 또는 호출되지 않기 때문에 정의를 제공 할 때 아무런 의미가 없습니다. 기본 클래스 생성자는 해당 가상 함수를 호출합니다.


9
이것은 올바르지 않습니다. 순수한 가상 클래스의 생성자에서 순수한 가상 함수를 호출하면 순수한 가상 호출이 수행됩니다. 어떤 경우에는 구현이 더 좋습니다.
— rmn

@rmn, 그렇습니다. 생성자의 가상 호출에 대한 것입니다. 나는 대답을 업데이트했다. 그래도 모든 사람들이 그렇게하지 않기를 바랍니다. :)
— Terry Mahaffey

3
실제로 생성자에서 기본 순수 호출을 수행하면 구현 정의 동작이 발생합니다. VC ++에서는 _purecall 충돌이 발생합니다.
— Ofek Shilon 2016 년

@OfekShilon 맞습니다-정의되지 않은 동작과 나쁜 연습 / 코드 리팩터링 (생성자 내부의 가상 메소드 호출)의 후보라고도 할 수 있습니다. 가상 테이블 일관성과 관련이 있으며 올바른 구현 본문으로 라우팅 할 준비가되지 않았을 수 있습니다.
— teodron

1
생성자와 소멸자에서 가상 함수는 가상 이 아닙니다 .
— Jesper Juhl

20

이 방법의 장점은 파생 형식이 여전히 메서드를 재정의하지만 기본 또는 추가 구현을 제공한다는 것입니다.


1
기본 구현이있는 경우 왜 강제하고 싶습니까? 일반적인 가상 기능처럼 들립니다. 그것이 단지 정상적인 가상 함수라면, 재정의 할 수 있고 그렇지 않은 경우 기본 구현이 제공됩니다 (기본 구현).
— StackExchange123

19

파생 클래스에서 실행해야하는 코드가 있지만 직접 실행하지 않으려는 경우 강제로 재정의하려는 경우

코드는 정확하지만 모든 것이 자주 사용되는 기능은 아니지만 일반적으로 순수한 가상 소멸자를 정의하려고 할 때만 나타납니다.이 경우 구현을 제공 해야 합니다. 재미있는 점은 일단 그 클래스에서 파생되면 소멸자를 재정의 할 필요가 없다는 것입니다.

따라서 순수한 가상 함수의 현명한 사용법은 순수한 가상 소멸자를 "최종"키워드로 지정하는 것입니다.

다음 코드는 놀랍도록 정확합니다.

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
기본 클래스 소멸자는 항상 어쨌든 가상이든 아니든 순수하거나 불리거나 불려집니다. 다른 함수를 사용하면 기본 클래스 버전이 순수한지 여부에 관계없이 재정의 가상 함수가 기본 클래스 구현을 호출한다고 보장 할 수 없습니다.
— CB Bailey

1
그 코드가 잘못되었습니다. 언어의 구문 문제로 인해 클래스 정의 외부에서 dtor를 정의해야합니다.

@Roger : 고맙습니다. 실제로 도움이되었습니다. 이것은 내가 사용하고있는 코드이며 MSVC에서 잘 컴파일되지만 이식성이 없을 것 같습니다.
— Kornel Kisielewicz


4

네 맞습니다. 예제에서 A에서 파생 된 클래스는 인터페이스 f ()와 기본 구현을 모두 상속합니다. 그러나 파생 클래스가 f () 메소드를 구현하도록 강제합니다 (A에서 제공하는 기본 구현 만 호출하더라도).

Scott Meyers는 Effective C ++ (제 2 판) 항목 # 36에서이를 설명합니다. 인터페이스 상속과 구현 상속을 구분합니다. 최신 버전에서 품목 번호가 변경되었을 수 있습니다.


4

본문이 있거나없는 순수한 가상 함수는 파생 된 형식이 자체 구현을 제공해야 함을 의미합니다.

기본 클래스의 순수 가상 함수 본문은 파생 클래스가 기본 클래스 구현을 호출하려는 경우 유용합니다.


2

'가상 무효 foo () = 0;' 구문은 현재 클래스에서 foo ()를 구현할 수 없다는 것을 의미하지는 않습니다. 또한 파생 클래스에서 구현해야한다는 의미는 아닙니다 . 당신이 나를 때리기 전에, 다이아몬드 문제를 관찰 해 봅시다 :

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

이제 obj-> foo () 호출로 B :: foo () 및 C :: bar ()가 발생합니다.

알다시피 ... 순수 가상 메소드는 파생 클래스에서 구현할 필요가 없습니다 (foo ()는 C 클래스에서 구현되지 않습니다-컴파일러는 컴파일됩니다) C ++에는 많은 허점이 있습니다.

내가 도울 수 있기를 바랍니다 :-)


5
모든 파생 클래스에서 구현할 필요는 없지만 인스턴스화하려는 모든 파생 클래스에서 구현해야합니다. C예제에서 유형의 객체를 인스턴스화 할 수 없습니다 . 에서 D구현을 가져 foo오기 때문에 유형의 객체를 인스턴스화 할 수 있습니다 B.
— YoungJohn

0

구현 본문과 함께 순수한 가상 메소드 를 사용하는 한 가지 중요한 유스 케이스 는 추상 클래스를 원하지만 클래스에 순수 가상으로 만들 적절한 메소드가없는 것입니다. 이 경우 클래스의 소멸자를 순수 가상으로 만들고 원하는 구현 (빈 바디)을 넣을 수 있습니다. 예로서:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

이 기술은 Foo클래스를 추상화하여 결과적으로 클래스를 직접 인스턴스화 할 수 없습니다. 동시에 Foo클래스 추상화 를위한 추가 순수 가상 메소드를 추가하지 않았습니다 .

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