2010-05-28 2 views
36

함수 앞에 예약어 virtual을 사용하는 목적은 무엇입니까? 하위 클래스에서 부모 함수를 재정의하려면 void draw(){}과 같은 함수를 선언하기 만하면됩니다.가상 대가비시

class Parent { 
public: 
    void say() { 
     std::cout << "1"; 
    } 
}; 

class Child : public Parent { 
public: 
    void say() 
    { 
     std::cout << "2"; 
    } 
}; 

int main() 
{ 
    Child* a = new Child(); 
    a->say(); 
    return 0; 
} 

출력은, 왜 예약어 say()의 헤더에 필요한 virtual 것이다 그래서 다시

2.입니까?

감사합니다.

답변

17

이것은 내가 다형성이 어떻게 작용하는지에 대한 고전적인 질문입니다. 주요 개념은 각 객체의 특정 유형을 추상화하려는 것입니다. 다시 말해, 자식 인스턴스가 아니라도 자식 인스턴스를 호출 할 수 있기를 원합니다.

예 : "Child"클래스와 "Child2"및 "Child3"클래스가 있고 기본 클래스 (Parent)를 통해 참조 할 수 있기를 원한다고 가정합니다.

Parent[3] parents; 
parents[0] = new Child(); 
parents[1] = new Child2(); 
parents[2] = new Child3(); 

for (int i=0;i<3;++i) 
parents[i]->say(); 

이것은 상상할 수있는 것처럼 매우 강력합니다. 부모가 원하는만큼 여러 번 Parent를 확장하고 Parent 포인터를 사용하는 함수는 계속 작동합니다. 다른 사람들이 언급 한대로 작동하려면 메서드를 가상으로 선언해야합니다.

+0

명백한 예가 많이 인정되었을 것입니다. – jokoon

2

가상 키워드를 사용하면 인스턴스에 올바른 메서드를 찾기위한 가상 함수 테이블이 만들어집니다. 그런 다음 파생 된 인스턴스가 기본 클래스 포인터에 의해 가리키는 경우에도 여전히 메서드의 올바른 구현을 찾습니다. 함수가 가상이라면

37

, "2"를 당신은이 작업을 수행 할 수 있으며, 여전히 출력을 얻을 :

Parent* a = new Child(); 
a->say(); 

virtual 기능이 아닌 가상 함수가 사용하는 반면 실제 유형을 사용하기 때문에이 작동 유형으로 선언되었습니다. 왜 이것을하고 싶은지에 대한 더 자세한 설명은 polymorphism을 참조하십시오.

+0

'Parent'가 말하자면 'Shape'이고 자식이 특정 유형의 도형 (예 : 'Square') 인 전형적인 예가 항상 있습니다. 그런 다음'say '를'draw'로 대체하십시오. 그게 왜 도움이 될지 아십니까? 그것은 OP의 질문에서와 똑같은 예제입니다. 다른 단어들입니다. – Donnie

+0

좋은 예! ...하지만 왜이 모든 시간을합니까? 왜 Square * sq = new Square(); 처음부터? – user1511417

+3

항상 그렇게하지는 마시고, 적절한시기에 해보십시오. 드로잉 앱을 만들고 사람들이 셰이프 브러시를 선택하게한다면 어떨까요? 전역 변수 (또는 적어도 객체 수준의 변수)가 필요하지만 사전에 어떤 모양을 선택할지 모릅니다. – Donnie

24

으로 시도 :

virtual없이
Parent *a = new Child(); 
Parent *b = new Parent(); 

a->say(); 
b->say(); 

, 모두 인쇄와 '1'. 가상을 추가하면 자식은 Parent에 대한 포인터를 통해 참조되는 경우에도 자식처럼 작동합니다.

+0

개체를 캐스팅 할 때 또는 파생 생성자를 사용할 때를 제외하면 재정의 된 일반 메서드와 오버로드 된 가상 메서드의 차이점을 알 수있는 방법이 없습니다. – jokoon

+0

이것이 오버 라이드와 가상의 차이점을 설명 할 때 사용하는 가장 좋은 예라고 생각합니다. – rkioji

0

이것은 C++ 프로그래밍의 매우 중요한 부분입니다. 거의 모든 인터뷰에서이 질문을 받았습니다. 당신이 당신의 주요 변경하면 어떻게됩니까

: 또한

int main() { Parent* a = new Child(); a->say(); return 0; } 

, 그것은 VTABLE가 무엇인지 이해하는 가치가있다.

14

virtual 키워드를 사용하지 않으면 기본 클래스 방법을 숨길 수있는 파생 클래스에서 관련이없는 메서드를 정의하는 데 rahter가 우선합니다. 즉, virtual이 없으면 Base::sayDerived::say은 이름과 일치하지 않습니다.

가상 키워드 (파생 클래스에서 선택 사항 인 기본에서 필수)를 사용하면이 기본 클래스에서 파생되는 클래스가 이 메서드를 재정의 할 수 있음을 컴파일러에 알리고 있습니다. 이 경우 Base::sayDerived::say은 같은 방법의 재정의로 간주됩니다.

기본 클래스에 대한 참조 또는 포인터를 사용하여 가상 메서드를 호출하면 컴파일러에서 최종 재정의자인 (메서드를 정의하는 대부분의 파생 클래스의 재정의)이 호출되도록 적절한 코드를 추가합니다. 사용중인 구체적인 인스턴스의 계층 구조). reference/pointer하지만 로컬 변수를 사용하지 않으면 컴파일러가 호출을 해석 할 수 있으며 가상 디스패치 메커니즘을 사용할 필요가 없다는 점에 유의하십시오.

11

이 잘 나는 많은 일이 있기 때문에 우리가 생각할 수있는, 자신을 위해 그것을 테스트에 대한 :

#include <iostream> 
using namespace std; 
class A 
{ 
public: 
    virtual void v() { cout << "A virtual" << endl; } 
    void f() { cout << "A plain" << endl; } 
}; 

class B : public A 
{ 
public: 
    virtual void v() { cout << "B virtual" << endl; } 
    void f() { cout << "B plain" << endl; } 
}; 

class C : public B 
{ 
public: 
    virtual void v() { cout << "C virtual" << endl; } 
    void f() { cout << "C plain" << endl; } 
}; 

int main() 
{ 
    A * a = new C; 
    a->f(); 
    a->v(); 

    ((B*)a)->f(); 
    ((B*)a)->v(); 
} 

출력 :

A plain 
C virtual 
B plain 
C virtual 

나는, 좋은 간단하고 짧은 대답이 수도 있다고 생각 (더 많은 것을 이해할 수있는 사람들이 덜 기억할 수 있기 때문에 짧고 간단한 설명이 필요합니다.)

가상 메소드는 포인터의 데이터를 확인합니다. 반면에 고전적인 방법은 지정된 유형에 상응하는 방법을 호출하지 않습니다.

해당 기능의 요점은 다음과 같습니다. A의 배열이 있다고 가정합니다. 배열에는 B, C, 또는 파생 된 유형이 포함될 수 있습니다. 모든 인스턴스의 동일한 메소드를 순차적으로 호출하려면 오버로드 된 인스턴스를 각각 호출해야합니다.

나는 이것을 이해하기가 매우 까다 롭다. 분명히 모든 C++ 과정은 이것이 어떻게 달성되는지 설명해야한다. 왜냐하면 대부분의 경우 가상 함수에 대해 가르쳐야하기 때문에 컴파일러가이를 이해하는 방법을 이해할 때까지 실행 파일이 호출을 처리하는 방법, 당신은 어둠 속에 있습니다.

VFtables에 대한 것은 내가 어떤 종류의 코드를 추가했는지 설명하지 못했고 C++은 C보다 많은 경험이 필요하며 분명히 C++이 "느리게"라는 레이블이 붙은 이유 일 것입니다. 초기에는 강력합니다. 그러나 모든 것과 마찬가지로, 사용법을 아는 것이 강력합니다. 그렇지 않으면 단지 "다리를 날려 버리십시오".

+1

좋은 코드 예 +1 – fusi