C++ 초급 - 8. 객체지향 프로그래밍 (2 - virtual 함수와 오버라이딩 (Overriding))

2025. 2. 22. 15:04프로그래밍 언어/C++

📌 8.2 virtual 함수와 오버라이딩 (Overriding)

C++에서 가상 함수(virtual function)동적 바인딩(Dynamic Binding)을 활용하여,
파생 클래스(자식 클래스)에서 부모 클래스의 함수를 재정의(Overriding)할 수 있도록 하는 기능
이다.
이를 통해 다형성(Polymorphism)을 구현하고, 객체지향 프로그래밍(OOP)의 핵심 개념을 실현할 수 있다.


📌 1. 가상 함수 (virtual function)의 개념과 필요성

🔹 (1) 정적 바인딩(Static Binding) vs. 동적 바인딩(Dynamic Binding)

  • 정적 바인딩(Static Binding): 컴파일 타임에 호출할 함수를 결정.
  • 동적 바인딩(Dynamic Binding): 런타임에 객체의 타입을 기준으로 호출할 함수를 결정.

💡 예제: 정적 바인딩 (virtual 없는 경우)

#include <iostream>

class Base {
public:
    void showMessage() { std::cout << "Base 클래스 함수 호출!" << std::endl; }
};

class Derived : public Base {
public:
    void showMessage() { std::cout << "Derived 클래스 함수 호출!" << std::endl; }
};

int main() {
    Base* ptr = new Derived();  // 부모 클래스 포인터로 자식 객체 생성
    ptr->showMessage();  // Base 클래스의 함수 호출 (정적 바인딩)

    delete ptr;
    return 0;
}

🔹 출력 결과

Base 클래스 함수 호출!

💡 설명

  • ptr이 Derived 객체를 가리키지만, showMessage()는 컴파일 타임(정적 바인딩)에서 Base 클래스의 함수가 호출됨.
  • 이를 해결하려면 virtual 키워드를 사용하여 동적 바인딩을 활성화해야 함.

📌 2. virtual 키워드를 사용하여 가상 함수 선언

🔹 (1) virtual 함수 적용

부모 클래스의 함수에 virtual 키워드를 붙이면, 자식 클래스에서 해당 함수를 재정의할 수 있으며, 포인터를 통해 호출할 경우 올바른 함수가 실행됨 (동적 바인딩 적용).

💡 예제: 가상 함수(virtual)을 사용한 동적 바인딩

#include <iostream>

class Base {
public:
    virtual void showMessage() {  // 가상 함수 선언
        std::cout << "Base 클래스 함수 호출!" << std::endl;
    }
};

class Derived : public Base {
public:
    void showMessage() override {  // 오버라이딩
        std::cout << "Derived 클래스 함수 호출!" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();  // 부모 클래스 포인터로 자식 객체 생성
    ptr->showMessage();  // Derived 클래스의 함수 호출 (동적 바인딩)

    delete ptr;
    return 0;
}

🔹 출력 결과

Derived 클래스 함수 호출!

💡 설명

  • Base 클래스에서 virtual 키워드를 사용하여 가상 함수 선언.
  • Derived 클래스에서 showMessage()를 오버라이딩(override)하여 재정의.
  • Base* ptr = new Derived(); → ✅ 동적 바인딩이 적용되어 Derived 클래스의 함수가 호출됨.

📌 3. 오버라이딩 (Overriding)의 구현 방법

🔹 (1) 기본 클래스와 파생 클래스에서 같은 함수명이 있을 때의 동작 방식

  • **부모 클래스의 가상 함수(virtual function)**는 자식 클래스에서 재정의 가능.
  • 부모 포인터(Base*)를 통해 호출하면 자식 클래스의 오버라이딩된 함수가 실행됨 (동적 바인딩).

💡 예제: 오버라이딩된 함수 확인

#include <iostream>

class Base {
public:
    virtual void show() { std::cout << "Base 클래스" << std::endl; }
};

class Derived : public Base {
public:
    void show() override { std::cout << "Derived 클래스" << std::endl; }
};

int main() {
    Base baseObj;
    Derived derivedObj;

    Base* basePtr = &baseObj;
    Base* derivedPtr = &derivedObj;

    basePtr->show();  // Base 클래스 함수 호출
    derivedPtr->show();  // Derived 클래스 함수 호출 (동적 바인딩)

    return 0;
}

🔹 출력 결과

Base 클래스
Derived 클래스

💡 설명

  • Base의 show()는 virtual 함수 → ✅ derivedPtr->show();에서 동적 바인딩이 적용되어 Derived의 함수가 실행됨.

📌 4. 포인터와 참조를 이용한 다형성 구현

🔹 (1) 기본 클래스 포인터를 활용한 다형성

  • 부모 클래스의 포인터 또는 참조를 사용하여 자식 클래스 객체를 관리 가능.
  • 런타임에 올바른 함수가 호출됨 (동적 바인딩 적용).

💡 예제: 포인터를 이용한 다형성

#include <iostream>

class Animal {
public:
    virtual void sound() { std::cout << "동물 소리!" << std::endl; }
};

class Dog : public Animal {
public:
    void sound() override { std::cout << "멍멍!" << std::endl; }
};

class Cat : public Animal {
public:
    void sound() override { std::cout << "야옹!" << std::endl; }
};

int main() {
    Animal* a1 = new Dog();
    Animal* a2 = new Cat();

    a1->sound();  // 멍멍!
    a2->sound();  // 야옹!

    delete a1;
    delete a2;
    return 0;
}

🔹 출력 결과

멍멍!
야옹!

💡 설명

  • Animal 클래스의 sound()를 가상 함수(virtual)로 선언.
  • Dog와 Cat에서 sound()를 오버라이딩.
  • 부모 포인터(Animal* a1 = new Dog();)로 다형성 구현.

📌 5. 가상 소멸자 (virtual ~Base())의 필요성

🔹 (1) 기본적으로 부모 클래스의 소멸자가 virtual이 아닌 경우

  • 부모 클래스 포인터를 통해 자식 클래스 객체를 삭제할 때, 자식 클래스의 소멸자가 호출되지 않을 수 있음.
  • 메모리 누수가 발생할 수 있음.

💡 예제: 가상 소멸자가 없는 경우 (메모리 누수 발생)

#include <iostream>

class Base {
public:
    ~Base() { std::cout << "Base 소멸자 호출!" << std::endl; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived 소멸자 호출!" << std::endl; }
};

int main() {
    Base* ptr = new Derived();
    delete ptr;  // ❌ Base 소멸자만 호출됨 (메모리 누수 발생 가능)

    return 0;
}

🔹 출력 결과

Base 소멸자 호출!

💡 해결 방법부모 클래스 소멸자를 virtual로 선언

class Base {
public:
    virtual ~Base() { std::cout << "Base 소멸자 호출!" << std::endl; }
};

🔹 출력 결과

Derived 소멸자 호출!
Base 소멸자 호출!

💡 설명

  • virtual ~Base(); → 자식 클래스의 소멸자도 호출되도록 보장하여 메모리 누수 방지.

📌 6. 정리

개념 설명
가상 함수(virtual) 동적 바인딩을 활성화하여 다형성 구현
오버라이딩(Overriding) 파생 클래스에서 부모 클래스 함수를 재정의
포인터와 참조 기본 클래스 포인터를 사용하여 다형성 활용 가능
가상 소멸자(virtual ~Base()) 자식 클래스 소멸자까지 올바르게 호출