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()) | 자식 클래스 소멸자까지 올바르게 호출 |
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 초급 - 8. 객체지향 프로그래밍 (4 - 인터페이스 개념 (Interface)) (0) | 2025.02.22 |
---|---|
C++ 초급 - 8. 객체지향 프로그래밍 (3 - 순수 가상 함수 (= 0)) (0) | 2025.02.22 |
C++ 초급 - 8. 객체지향 프로그래밍 (1 - 캡슐화(Encapsulation), 상속(Inheritance), 다형성(Polymorphism)) (0) | 2025.02.22 |
C++ 초급 - 7. 구조체와 클래스 (6 - const 멤버 함수) (0) | 2025.02.22 |
C++ 초급 - 7. 구조체와 클래스 (5 - this 포인터) (0) | 2025.02.22 |