가상 함수(가상 메서드라고도 함)는 기본 클래스 내에서 선언되고 파생 클래스에 의해 다시 정의(재정의)되는 멤버 함수입니다. 기본 클래스에 대한 포인터나 참조를 사용하여 파생 클래스 개체를 참조하면 해당 개체에 대한 가상 함수를 호출하고 파생 클래스 버전의 메서드를 실행할 수 있습니다.
- 가상 함수는 함수 호출에 사용된 참조(또는 포인터) 유형에 관계없이 객체에 대해 올바른 함수가 호출되도록 합니다.
- 이들은 주로 런타임 다형성을 달성하는 데 사용됩니다.
- 함수는 다음과 같이 선언됩니다. 가상 기본 클래스의 키워드.
- 함수 호출의 해결은 런타임에 수행됩니다.
가상 기능에 대한 규칙
C++의 가상 함수에 대한 규칙은 다음과 같습니다.
- 가상 기능은 정적일 수 없습니다.
- 가상 함수는 다른 클래스의 친구 함수일 수 있습니다.
- 런타임 다형성을 달성하려면 기본 클래스 유형의 포인터나 참조를 사용하여 가상 함수에 액세스해야 합니다.
- 가상 함수의 프로토타입은 기본 클래스와 파생 클래스에서 동일해야 합니다.
- 이는 항상 기본 클래스에서 정의되고 파생 클래스에서 재정의됩니다. 파생 클래스가 가상 함수를 재정의(또는 다시 정의)하는 것은 필수가 아니며, 이 경우 함수의 기본 클래스 버전이 사용됩니다.
- 클래스에는 가상 소멸자가 있을 수 있지만 가상 생성자는 있을 수 없습니다.
가상 함수의 컴파일 시간(초기 바인딩) VS 런타임(후기 바인딩) 동작
가상 기능의 런타임 동작을 보여주는 다음과 같은 간단한 프로그램을 고려해보세요.
C++
// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class
'>; }> >void> show() { cout <<>'show base class
'>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class
'>; }> >void> show() { cout <<>'show derived class
'>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->인쇄();> >// Non-virtual function, binded at compile time> >bptr->쇼();> >return> 0;> }> |
>
문자 비교 자바
>산출
print derived class show base class>
설명: 런타임 다형성은 기본 클래스 유형의 포인터(또는 참조)를 통해서만 달성됩니다. 또한 기본 클래스 포인터는 기본 클래스의 개체뿐만 아니라 파생 클래스의 개체도 가리킬 수 있습니다. 위 코드에서 기본 클래스 포인터 'bptr'에는 파생 클래스의 객체 'd'의 주소가 포함되어 있습니다.
런타임 바인딩(런타임)은 포인터의 내용(포인터가 가리키는 위치)에 따라 수행되고, 초기 바인딩(컴파일 타임)은 print() 함수가 가상 변수로 선언되므로 포인터의 유형에 따라 수행됩니다. 키워드이므로 런타임에 바인딩됩니다(출력은 파생 클래스 인쇄 포인터가 파생 클래스의 객체를 가리키고 있으므로 show()는 비가상이므로 컴파일 시간 동안 바인딩됩니다(출력은 기본 클래스 표시 포인터가 기본 유형이므로).
메모: 기본 클래스에서 가상 함수를 만들었고 파생 클래스에서 재정의되는 경우 파생 클래스에 virtual 키워드가 필요하지 않으며 함수는 파생 클래스에서 자동으로 가상 함수로 간주됩니다.
가상 기능 작업(VTABLE 및 VPTR의 개념)
여기에서 설명한 대로 클래스에 가상 함수가 포함되어 있으면 컴파일러 자체에서 두 가지 작업을 수행합니다.
- 해당 클래스의 객체가 생성되면 가상 포인터(VPTR) 해당 클래스의 VTABLE을 가리키도록 클래스의 데이터 멤버로 삽입됩니다. 새로운 객체가 생성될 때마다 새로운 가상 포인터가 해당 클래스의 데이터 멤버로 삽입됩니다.
- 객체 생성 여부에 관계없이 클래스는 멤버로 포함됩니다. VTABLE이라는 함수 포인터의 정적 배열 . 이 테이블의 셀에는 해당 클래스에 포함된 각 가상 함수의 주소가 저장됩니다.
아래 예를 고려하십시오.

C++
// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1
'>; }> >virtual> void> fun_2() { cout <<>'base-2
'>; }> >virtual> void> fun_3() { cout <<>'base-3
'>; }> >virtual> void> fun_4() { cout <<>'base-4
'>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1
'>; }> >void> fun_2() { cout <<>'derived-2
'>; }> >void> fun_4(>int> x) { cout <<>'derived-4
'>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->fun_1();> >// Late binding (RTP)> >p->fun_2();> >// Late binding (RTP)> >p->fun_3();> >// Late binding (RTP)> >p->fun_4();> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->fun_4(5);> >return> 0;> }> |
>
>산출
base-1 derived-2 base-3 base-4>
설명: 처음에는 기본 클래스 유형의 포인터를 만들고 파생 클래스 개체의 주소로 초기화합니다. 파생 클래스의 개체를 만들 때 컴파일러는 파생 클래스의 VTABLE 주소를 포함하는 클래스의 데이터 멤버로 포인터를 만듭니다.
비슷한 개념의 후기 및 초기 바인딩 위의 예와 같이 사용됩니다. fun_1() 함수 호출의 경우 함수의 기본 클래스 버전이 호출되고, fun_2()는 파생 클래스에서 재정의되므로 파생 클래스 버전이 호출되며, fun_3()은 파생 클래스에서 재정의되지 않으며 가상 함수입니다. 따라서 기본 클래스 버전이 호출되고 마찬가지로 fun_4()가 재정의되지 않으므로 기본 클래스 버전이 호출됩니다.
메모: 파생 클래스의 fun_4(int)는 기본 클래스의 가상 함수 fun_4()와 다릅니다. 두 함수의 프로토타입이 다르기 때문입니다.
가상 기능의 한계
- 느림: 가상 메커니즘으로 인해 함수 호출이 약간 더 오래 걸리고 컴파일 타임에 어떤 함수가 호출될지 정확히 알 수 없기 때문에 컴파일러가 최적화하기가 더 어렵습니다. 디버그하기 어려움: 복잡한 시스템에서 가상 함수를 사용하면 함수가 호출되는 위치를 파악하기가 좀 더 어려울 수 있습니다.