가상함수란?

가상함수는 부모 클래스에서 상속받을 클래스에서 재정의할 것으로 기대하고 정의해놓은 함수입니다. virtual이라는 예약어를 함수 앞에 붙여서 생성할 수 있으며 이렇게 생성된 가상함수는 파생 클래스에서 재정의하면 이전에 정의되었던 내용들은 모두 새롭게 정의된 내용들로 교체됩니다.

가상함수를 사용해야 하는 이유

컴파일러는 함수를 호출할 때 매우 복잡한 과정을 거치게 됩니다. 그렇기에 컴파일러는 함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환시킵니다. 이것을 정적바인딩이라고 합니다. 일반 함수의 경우 모두 이러한 정적바인딩을 하게 됩니다. 하지만 일반 함수를 오버 로딩하게 되면 정적바인딩으로 인해 문제가 될 수 있습니다. 가상함수가 아닌 보통의 경우 부모 클래스형 포인터로 멤버 함수를 호출할때, 컴파일러는 정적타입을 보고 이 타입에 맞는 멤버함수를 호출하기 때문입니다.

#include <iostream>
using namespace std;

class Parent {
public:
		void print() {
		   cout << "이곳은 parent입니다." << endl;
		}
};
class Child : public Parent {
public:
		void print() {
				cout << "이곳은 child입니다." << endl;
		}
};

void main() {
    Parent *p = new Parent;
    Child *c = new Child;
    
    p -> print();
    p = c;
    p -> print();
}

/* 출력 */
이곳은 Parent입니다.
이곳은 Parent입니다.

위의 예제를 보시면 Parent타입으로 선언된 포인터 p에 Child객체의 주소를 넣고 함수를 호출시켰는데 Parent클래스의 함수가 호출되었습니다. p포인터의 주소를 child로 바꾸어주었음에도 불구하고 정적바인딩으로 인해(컴파일 당시 호출될 함수의 번지가 이미 결정나버렸기 때문에) 부모의 함수가 호출되는 것입니다. 이를 해결하려면 정적바인딩이 아닌 동적바인딩을 해야 합니다. 동적바인딩을 하려면 일반 함수들을 가상함수로 바꾸어주시면 됩니다. 가상함수로 선언하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택하게 됩니다.

가상함수 virtual 사용법

virtual 반환형식 메서드명
virtual void PrintName();
#include <iostream>
using namespace std;

class Parent{
public:
    virtual void print() { 
        cout << "이곳은 Parent입니다." << endl;
    }
};

class Child : public Parent{
public:
    virtual void print() {
        cout << "이곳은 Child입니다." << endl;
    }
};

void main() {
    Parent* p = new Parent;
    Child* c = new Child;
    
    p->print();
    p = c;
    p->print();
}

가상함수 테이블

Virtual Function Table. 여기서 테이블은 배열

가상함수 테이블은 함수 포인터 배열이라고 생각하시면 됩니다.

#include <iostream>
using namespace std;

class Parent{
public:
    virtual void func1() {
        cout << "이곳은 Parent의 func1입니다." << endl;
    }
    virtual void func2() {
        cout << "이곳은 Parent의 func2입니다." << endl;
    }
    void func3() { //가상함수 X
        cout << "이곳은 Parent의 func3입니다." << endl;
    }
};

class Child : public Parent{
public:
    virtual void func1() {
        cout << "이곳은 Child의 func1입니다." << endl;
    }
};

void main() {
    Parent* p = new Parent;
    Child* c = new Child;
    
    p->func1();
    c->func1();
    p->func2();
    c->func2();
    p->func3();
    c->func3();
}
/* */
이곳은 Parent의 func1입니다.
이곳은 Child의 func1입니다.
이곳은 Parent의 func2입니다.
이곳은 Parent의 func2입니다.
이곳은 Parent의 func3입니다.
이곳은 Parent의 func3입니다.