가상 / 순수 가상 설명


346

함수가 가상으로 정의되고 순수 가상과 동일하다면 정확히 무엇을 의미합니까?

답변:


339

에서 위키 백과의 가상 기능 ...

C ++ 및 Object Pascal과 같은 언어로 된 객체 지향 프로그래밍에서 가상 함수 또는 가상 메소드는 상속 및 재정의 가능한 함수 또는 동적 디스패치가 용이 한 메소드입니다. 이 개념은 객체 지향 프로그래밍 (OOP)의 (런타임) 다형성 부분에서 중요한 부분입니다. 즉, 가상 함수는 실행할 대상 함수를 정의하지만 컴파일시 대상을 알 수 없습니다.

비가 상 함수와 달리 가상 함수가 재정의되면 가장 파생 된 버전은 생성 된 수준이 아니라 클래스 계층의 모든 수준에서 사용됩니다. 따라서 기본 클래스의 한 메소드가 가상 메소드를 호출 하면 기본 클래스에 정의 된 버전 대신 파생 클래스에 정의 된 버전이 사용됩니다.

이는 파생 클래스에서 여전히 재정의 될 수있는 비가 상 함수와 대조적이지만 "새"버전은 파생 클래스와 그 이하에서만 사용되지만 기본 클래스의 기능은 전혀 변경되지 않습니다.

이므로..

순수 가상 함수 또는 순수 가상 메소드는 파생 클래스가 추상이 아닌 경우 파생 클래스에서 구현해야하는 가상 함수입니다.

순수한 가상 메소드가 존재하면 클래스는 "추상적"이며 자체적으로 인스턴스화 할 수 없습니다. 대신 순수 가상 메소드를 구현하는 파생 클래스를 사용해야합니다. 순수 가상은 기본 클래스에 전혀 정의되어 있지 않으므로 파생 클래스 가이를 정의 해야합니다 . 그렇지 않으면 파생 클래스도 추상적이고 인스턴스화 할 수 없습니다. 추상 메소드가없는 클래스 만 인스턴스화 할 수 있습니다.

가상은 기본 클래스의 기능을 재정의하는 방법을 제공하며 순수 가상 에는 필수입니다.


10
그렇다면 ... 순수한 가상 키워드입니까, 아니면 사용되는 용어입니까?
저스틴

197
가상 무효 함수 () = 0; 순수한 가상입니다. "= 0"은 순도를 나타냅니다.
Goz

8
저스틴, '순수한 가상'은 "이 함수는 기본 클래스로 구현할 수 없습니다. Goz가 말했듯이 가상의 끝에"= 0 "을 추가하는 데 사용되는 용어입니다 (키워드가 아니라 아래 답변 참조). 기능은 "순수"합니다
Nick Haddad

14
Stroustrup은 pure키워드 를 추가하고 싶다고 말 했지만 Bell Labs는 C ++의 주요 릴리스를 만들려고했으며 그의 관리자는 그 말기에는이를 허용하지 않았습니다. 키워드를 추가하는 것이 중요합니다.
quark

14
이것은 좋은 대답이 아닙니다. 가상 방법뿐만 아니라 모든 방법을 무시할 수 있습니다. 자세한 내용은 내 답변을 참조하십시오.
Asik

212

여기 여러 곳에서 반복되는 Wikipedia의 가상 정의에 대해 언급하고 싶습니다. Wikipedia는 가상 메소드를 서브 클래스에서 재정의 할 수있는 가상 메소드로 정의했다. [다행스럽게도 위키 백과는 그 이후로 편집되었으며 이제는이를 올바르게 설명합니다.] 잘못된 것입니다. 가상 메서드뿐만 아니라 모든 메서드를 하위 클래스에서 재정의 할 수 있습니다. 가상은 다형성, 즉 런타임에 가장 파생 된 메서드 재정의를 선택할 수있는 기능을 제공하는 것 입니다.

다음 코드를 고려하십시오.

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

이 프로그램의 출력은 무엇입니까?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

파생은 가상의 방법뿐만 아니라 가상이 아닌 Base의 모든 방법을 재정의합니다.

Base-pointer-to-Derived (bDerived)가있을 때 비가 상을 호출하면 Base 클래스 구현이 호출됩니다. 이것은 컴파일 타임에 해결됩니다. 컴파일러는 bDerived가 Base *이고 NonVirtual이 가상이 아니므로 Base 클래스에서 확인을 수행합니다.

그러나 Virtual을 호출하면 Derived 클래스 구현이 호출됩니다. 키워드 virtual 때문에 메소드 선택은 컴파일 타임 이 아닌 런타임에 발생합니다 . 컴파일 타임에 여기에서 일어나는 일은 컴파일러가 이것이 Base *이고 가상 메서드를 호출한다는 것을 알기 때문에 Base 클래스 대신 vtable에 대한 호출을 삽입한다는 것입니다. 이 vtable은 런타임에 인스턴스화되므로 런타임 분석은 가장 파생 된 대체에 대한 것입니다.

나는 이것이 너무 혼란스럽지 않기를 바랍니다. 즉, 모든 메소드를 재정의 할 수 있지만 가상 메소드 만 다형성, 즉 가장 많이 파생 된 대체의 런타임 선택을 제공합니다. 그러나 실제로 비가 상 방법을 재정의하는 것은 나쁜 습관으로 간주되며 거의 사용되지 않으므로 많은 사람들 (Wikipedia 기사를 작성한 사람 포함)은 가상 방법 만 재정의 할 수 있다고 생각합니다.


6
Wikipedia 기사 (내가 방어하지는 않음)가 가상 메서드를 "서브 클래스에서 재정의 할 수있는 방법"으로 정의한다고해서 동일한 이름을 가진 다른 비 가상 메서드가 선언 될 가능성을 배제하지는 않습니다. 이것을 오버로딩이라고합니다.

26
그럼에도 불구하고 그 정의는 틀렸다. 파생 클래스에서 재정의 할 수있는 메서드는 정의상 가상이 아닙니다. 메소드를 재정의 할 수 있는지 여부는 "가상"의 정의와 관련이 없습니다. 또한 "오버로딩"은 일반적으로 동일한 클래스에서 이름과 반환 유형은 동일하지만 인수가 다른 여러 메서드를 갖는 것을 말합니다. "overrideing"과는 매우 다르며, 정확히 동일한 서명을 의미하지만 파생 클래스에 있습니다. 다형성이 아닌 (가상 기반이 아닌) 수행되는 경우가 종종 "숨김"이라고합니다.
Asik

5
이것은 정답입니다. 이 질문에 다른 사람이 없었기 때문에 여기에 링크하는 데 시간이 걸리는 특정 Wikipedia 기사 는 완전한 쓰레기입니다. +1, 좋습니다.
josaphatv

2
이제 말이 되네요. 파생 클래스가 모든 메소드를 대체 할 수 있으며 컴파일러가 다른 상황에서 호출되는 함수를 선택하는 방식에 변화가 있음을 올바르게 설명해 주셔서 감사합니다.
Doodad

3
Derived*동일한 기능 호출로을 추가 하여 포인트를 운전하는 것이 도움이 될 수 있습니다 . 그렇지 않으면 큰 대답
Jeff Jones

114

virtual 키워드는 C ++에 다형성을 지원할 수있는 기능을 제공합니다. 다음과 같은 클래스의 객체에 대한 포인터가있는 경우 :

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

이 예제에서, GetNumberOfLegs () 함수는 호출 된 객체의 클래스를 기반으로 적절한 숫자를 반환합니다.

이제 'SomeFunction'기능을 고려하십시오. Animal에서 파생되는 한 어떤 종류의 동물 개체가 전달되는지는 중요하지 않습니다. 컴파일러는 기본 클래스이므로 Animal 파생 클래스를 Animal에 자동으로 캐스트합니다.

우리가 이렇게하면 :

Duck d;
SomeFunction(&d);

'2'를 출력합니다. 우리가 이렇게하면 :

Horse h;
SomeFunction(&h);

'4'를 출력합니다. 우리는 이것을 할 수 없습니다 :

Animal a;
SomeFunction(&a);

GetNumberOfLegs () 가상 함수가 순수하기 때문에 컴파일되지 않기 때문에 클래스 (서브 클래스)를 파생시켜 구현해야합니다.

순수한 가상 함수는 주로 다음을 정의하는 데 사용됩니다.

a) 추상 수업

이것들은 그것들에서 파생 된 다음 순수 가상 함수를 구현해야하는 기본 클래스입니다.

b) 인터페이스

이들은 모든 함수가 순수한 가상 인 '빈'클래스이므로 모든 함수를 파생시킨 다음 구현해야합니다.


귀하의 예에서는 순수한 가상 방법의 구현을 제공하지 않았으므로 # 4를 수행 할 수 없습니다. 방법이 순수한 가상이기 때문에 엄격하지는 않습니다.
iheanyi

@iheanyi 기본 클래스의 순수 가상 메소드에 구현을 제공 할 수 없습니다. 따라서 사례 # 4는 여전히 오류입니다.
prasad

32

C ++ 클래스에서 virtual 은 메소드가 서브 클래스를 대체 (즉, 구현) 할 수 있음을 지정하는 키워드입니다. 예를 들면 다음과 같습니다.

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

이 경우 서브 클래스는 initShape 함수를 재정 의하여 특수한 작업을 수행 할 수 있습니다 .

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

순수 가상 이라는 용어 는 서브 클래스에 의해 구현되어야하고 기본 클래스에 의해 구현되지 않은 가상 함수를 말합니다. virtual 키워드 를 사용 하고 메소드 선언 끝에 = 0 을 추가 하여 메소드를 순수 가상으로 지정합니다 .

따라서 Shape :: initShape를 순수 가상으로 만들려면 다음을 수행하십시오.

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

클래스에 순수한 가상 메소드를 추가하면 클래스를 추상 기본 클래스로 만들어 인터페이스를 구현에서 분리하는 데 매우 편리합니다.


1
"서브 클래스에 의해 구현되어야하는 가상 함수"와 관련하여, 이것은 사실이 아니지만, 서브 클래스가 추상적이지 않은 경우에도 추상적입니다. 그리고 추상 클래스는 인스턴스화 할 수 없습니다. 또한 "기본 클래스로 구현할 수 없습니다"는 잘못된 것으로 보입니다. 기본 클래스 내에 구현을 추가하기 위해 코드를 수정하는 데 제한이 없기 때문에 "있지 않았다"는 것이 더 좋습니다.
NVRAM

2
그리고 "getName 함수는 서브 클래스에 의해 구현 될 수 없습니다"는 옳지 않습니다. 서브 클래스는 메소드를 구현할 수 있지만 (동일하거나 다른 서명 포함) 해당 구현은 메소드를 대체하지 않습니다. Circle을 서브 클래스로 구현하고 "std :: string Circle :: getName ()"을 구현 한 다음 Circle 인스턴스에 대해 두 메소드를 호출 할 수 있습니다. 그러나 Shape 포인터 또는 참조를 통해 사용되는 경우 컴파일러는 Shape :: getName ()을 호출합니다.
NVRAM

1
양쪽에 좋은 점이 있습니다. 이 예제에서 특별한 경우에 대해 논의하지 않으려 고 노력했습니다. 답변을 더 용서하도록 수정하겠습니다. 감사!
Nick Haddad

@NickHaddad 오래된 스레드이지만 왜 변수를 호출했는지 궁금합니다 m_name. 무슨 m_뜻입니까?
Tqn

1
@Tqn은 NickHaddad가 규칙을 따른다고 가정하면 m_name은 일반적으로 헝가리 표기법이라고하는 명명 규칙입니다. m은 구조 / 클래스의 정수를 나타냅니다.
Ketcomp

16

"가상"은 서브 클래스에서 메소드가 대체 될 수 있지만 기본 클래스에서 직접 호출 가능한 구현을 가지고 있음을 의미합니다. "Pure virtual"은 직접 호출 가능한 구현이없는 가상 방법임을 의미합니다. 이러한 메소드 상속 계층 구조에서 적어도 한 번 재정의 해야합니다 . 클래스에 구현되지 않은 가상 메소드가 있으면 해당 클래스의 오브젝트를 구성 할 수 없어 컴파일에 실패합니다.

@quark은 순수 가상 메소드 는 구현을 가질 있지만 순수 가상 메소드를 대체해야하므로 기본 구현을 직접 호출 할 수는 없습니다. 다음은 기본값을 가진 순수 가상 방법의 예입니다.

#include <cstdio>

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

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

의견에 따르면 컴파일 실패 여부는 컴파일러마다 다릅니다. GCC 4.3.3 이상에서는 컴파일되지 않습니다.

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

int main()
{
    A a;
    return 0;
}

산출:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()

클래스의 인스턴스를 인스턴스화하려면이를 재정의해야합니다. 인스턴스를 만들지 않으면 코드가 정상적으로 컴파일됩니다.
Glen

1
컴파일이 실패하지 않습니다. (순수한) 가상 메소드의 구현이 없으면 해당 클래스 / 객체를 인스턴스화 할 수 없습니다. 링크되지는 않지만 컴파일됩니다.
Tim

@Glen, @tim : 어느 컴파일러에서? 추상 클래스를 작성하는 프로그램을 컴파일하려고하면 컴파일되지 않습니다.
John Millikin

@John Compilation은 PVF가 포함 된 클래스의 인스턴스를 인스턴스화하려는 경우에만 실패합니다. 물론 이러한 클래스에 대한 포인터 또는 참조 값을 인스턴스화 할 수 있습니다.

5
또한 John은 다음과 같은 내용이 옳지 않습니다. " '순수한 가상'이란 구현이없는 가상 방법을 의미합니다." 순수한 가상 메소드 는 구현 가질 있습니다. 그러나 직접 호출 할 수는 없습니다. 하위 클래스 내에서 기본 클래스 구현을 재정의하고 사용해야합니다. 이를 통해 구현의 기본 부분을 제공 할 수 있습니다. 그러나 일반적인 기술은 아닙니다.
quark

9

가상 키워드는 어떻게 작동합니까?

Man이 기본 클래스라고 가정하고 Indian은 man에서 파생됩니다.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

do_work ()를 가상으로 선언하는 것은 단순히 다음을 의미합니다. 호출 할 do_work ()는 런타임에만 결정됩니다.

내가한다고 가정 해 봅시다.

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

virtual을 사용하지 않으면 호출하는 객체에 따라 컴파일러가 정적으로 결정하거나 정적으로 바인딩됩니다. 따라서 Man 객체가 do_work ()를 호출하면 Man 's do_work ()는 인디언 객체에 대한 포인트를 가지더라도

나는 가장 많이 투표 된 답변이 오도라고 생각합니다-가상이 파생 클래스에서 재정의 된 구현을 가질 수 있는지 여부에 관계없이 모든 방법. C ++에 대한 특정 참조를 사용하면 관련 함수의 런타임 (가상이 사용될 때) 바인딩 및 컴파일 시간 (가상이 사용되지 않지만 메소드가 재정의되고 기본 포인터가 파생 된 객체를 가리킬 때) 바인딩이 달라집니다.

또 다른 오해의 소지가있는 것 같습니다.

"Justin, 'pure virtual'은"이 함수는 기본 클래스로는 구현할 수 없습니다. "

이것은 잘못입니다! 순전히 가상 함수는 본문을 가질 수 있으며 구현 될 수 있습니다! 진실은 추상 클래스의 순수한 가상 함수를 정적으로 호출 할 수 있다는 것입니다! Bjarne Stroustrup과 Stan Lippman은 언어를 썼기 때문에 두 명의 훌륭한 저자입니다.


2
안타깝게도 답변이 공개되기 시작하면 다른 모든 답변은 무시됩니다. 심지어 그들은 더 나을 수 있습니다.
LtWorf

3

가상 함수는 기본 클래스에서 선언되고 파생 클래스에 의해 재정의되는 멤버 함수입니다. 가상 함수는 상속 순서대로 계층 적입니다. 파생 클래스가 가상 함수를 재정의하지 않으면 기본 클래스 내에 정의 된 함수가 사용됩니다.

순수한 가상 함수는 기본 클래스와 관련된 정의가없는 함수입니다. 기본 클래스에는 구현이 없습니다. 파생 클래스는이 함수를 재정의해야합니다.


2

정적 메소드 바인딩을 기본적으로 사용하는 Simula, C ++ 및 C #은 프로그래머가 특정 메소드가 가상 바인딩으로 동적 바인딩을 사용하도록 지정할 수 있습니다. 동적 메소드 바인딩은 객체 지향 프로그래밍의 핵심입니다.

객체 지향 프로그래밍에는 캡슐화, 상속 및 동적 메소드 바인딩의 세 가지 기본 개념이 필요합니다.

캡슐화를 통해 추상화의 구현 세부 사항을 간단한 인터페이스 뒤에 숨길 수 있습니다.

상속을 통해 새로운 추상화를 기존 추상화의 확장 또는 개선으로 정의하여 그 특성의 일부 또는 전부를 자동으로 얻을 수 있습니다.

동적 메소드 바인딩을 사용하면 이전 추상화가 필요한 컨텍스트에서 사용될 때도 새 추상화가 새 동작을 표시 할 수 있습니다.


1

가상 메서드는 클래스를 파생시켜 재정의 할 수 있지만 기본 클래스 (재정의되는 클래스)에서 구현이 필요합니다.

순수한 가상 메소드는 기본 클래스를 구현하지 않습니다. 파생 클래스로 정의해야합니다. 재정의 할 것이 없기 때문에 기술적으로 재정의 된 것이 올바른 용어가 아닙니다.

파생 클래스가 기본 클래스의 메소드를 대체 할 때 가상은 기본 Java 동작에 해당합니다.

순수 가상 메소드는 추상 클래스 내에서 추상 메소드의 동작에 해당합니다. 순수한 가상 메소드와 상수 만 포함하는 클래스는 인터페이스에 대한 cpp-pendant입니다.


0

순수한 가상 기능

이 코드를 사용해보십시오

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

anotherClass 클래스에서 sayHellow 함수를 제거하고 코드를 실행하십시오. 클래스에 순수한 가상 함수가 포함되어 있으면 해당 클래스에서 객체를 만들 수 없으며 상속 된 클래스에서 해당 함수를 구현해야합니다.

가상 기능

다른 코드를 사용해보십시오

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

여기에서 sayHellow 함수는 기본 클래스에서 가상으로 표시됩니다. 파생 클래스에서 함수를 검색하고 함수를 구현하려고 시도하는 컴파일러를 말합니다. 찾을 수 없으면 기본 함수를 실행하십시오.


하하, 여기서 잘못된 점을 이해하는 데 30 초가 걸렸습니다 ... HelloW :)
hans

0

"가상 함수 또는 가상 메소드는 동일한 서명을 가진 함수로 상속 클래스 내에서 동작을 대체 할 수있는 함수 또는 메소드"-Wikipedia

이것은 가상 기능에 대한 좋은 설명이 아닙니다. 멤버가 가상이 아니더라도 상속 클래스가이를 대체 할 수 있기 때문입니다. 직접 시도해 볼 수 있습니다.

함수가 기본 클래스를 매개 변수로 사용하는 경우 차이점이 표시됩니다. 상속 클래스를 입력으로 제공하면 해당 함수는 재정의 함수의 기본 클래스 구현을 사용합니다. 그러나 해당 기능이 가상 인 경우 파생 클래스에서 구현 된 기능을 사용합니다.


0
  • 가상 함수는 기본 클래스와 파생 클래스에도 정의가 있어야하지만 필수는 아닙니다. 예를 들어 ToString () 또는 toString () 함수는 가상이므로 사용자 정의 클래스에서 재정 의하여 고유 한 구현을 제공 할 수 있습니다.

  • 가상 함수는 일반 클래스에서 선언되고 정의됩니다.

  • 순수 가상 함수는 "= 0"으로 끝나는 것으로 선언해야하며 추상 클래스에서만 선언 할 수 있습니다.

  • 순수한 가상 함수를 갖는 추상 클래스는 순수한 가상 함수의 정의를 가질 수 없으므로, 해당 추상 클래스에서 파생 된 클래스에서 구현이 제공되어야 함을 의미합니다.


@rashedcs와 같은 메모 : 실제로 순수한 가상 함수는 그 정의를 가질 수 있습니다.
Jarek C
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.