'가상 함수'는 'virtual' 예약어를 앞에 붙여서 선언한 메서드를 말합니다.

바로 예제를 살펴보겠습니다.

// 기본 클래스
class MyData {
protected:
    int m_nData = 10;
    
public:
    MyData() { /* do somthing */ }
    
    virtual void PrintData() {
        cout << m_nData << endl;
    }
    
    void Func() {
        PrintData();
    }
};

// 파생 클래스
class MyDataEx : public MyData {
public:
    virtual void PrintData() {
        cout << m_nData * 2 << endl;
    }
};

void main() {
    MyDataEx a;
    a.PrintData();
    
    MyData& b = a; // '부모 클래스' 타입의 참조자는 '자식 클래스' 인스턴스를 참조합니다.
    b.PrintData(); // 당연히 '부모 클래스'의 PrintData() 함수가 호출될 것이라 예상되지만,
                   // '가상 함수'는 '실 형식'의 메서드를 호출합니다.
                   
    a.Func(); // 여기서 PrintData() 함수는 당연히 '파생 클래스'에서 재정의된 것이 호출됩니다.
}

 

위의 코드에서 핵심은 'b.PrintData()' 부분입니다. 'b' 참조변수의 타입이 '기본 클래스'이기에 당연히 해당 클래스에 정의되어있는 PrintData() 함수가 호출될것 같지만, '가상 함수'는 '실 형식'의 메서드가 호출됩니다. 그리고 눈여겨봐야할 코드가 마지막의 'a.Func()' 입니다. '기본 클래스'의 Func() 함수를 호출했는데, Func() 함수 내에선 PrintData() 함수를 호출합니다. 그런데 재미있는 것은, PrintData() 함수는 '가상 함수'로 파생 클래스에서 재정의되었기 때문에 재정의된 함수가 호출된다는 것입니다. 이 말은 다른 말로, 내가 어떤 클래스를 상속받아서 '가상 함수'를 재정의했다면 나도 모르게 재정의한 '가상 함수'가 호출될 수 있다는 것입니다.

 

이러한 이유로 언젠가 '제작자' 입장에서 코드를 설계해야 하는 때가 온다면 특정 '가상 함수'가 미래에 재정의되는 것을 막아야 할 수도 있습니다. 그럴 경우에는 자신이 재정의한 '가상 함수' 뒤에 'final' 예약어를 붙여줍니다.

virtual void PrintData() final;