Topic 2: 가상 함수와 동적 바인딩 🎭
🎯 학습 목표
- 가상 함수의 개념과 필요성을 이해할 수 있다
- 정적 바인딩과 동적 바인딩의 차이를 설명할 수 있다
- 오버라이딩을 올바르게 구현할 수 있다
- 가상 함수 테이블(VTable)의 동작 원리를 이해할 수 있다
🎭 변신 로봇으로 이해하는 다형성
**다형성(Polymorphism)**은 마치 변신 로봇과 같습니다!
하나의 리모컨, 여러 동작 🎮
// 리모컨의 "변신" 버튼을 누르면...
// - 자동차 로봇 → 자동차로 변신
// - 비행기 로봇 → 비행기로 변신
// - 기차 로봇 → 기차로 변신
// 같은 명령, 다른 결과!
Robot* myRobot = new CarRobot();
myRobot->transform(); // 자동차로 변신!
myRobot = new PlaneRobot();
myRobot->transform(); // 비행기로 변신!🔍 정적 바인딩 vs 동적 바인딩
문제 상황: virtual이 없을 때
#include <iostream>
using namespace std;
// virtual이 없는 경우 - 정적 바인딩
class Animal {
public:
void makeSound() { // virtual이 없음!
cout << "동물이 소리를 냅니다" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() { // 오버라이딩...인 줄 알았지만!
cout << "멍멍! 🐕" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() {
cout << "야옹~ 🐱" << endl;
}
};
void testWithoutVirtual() {
cout << "=== virtual 없이 (정적 바인딩) ===" << endl;
Dog myDog;
Cat myCat;
Animal* animal1 = &myDog;
Animal* animal2 = &myCat;
// 문제 발생! 둘 다 Animal의 함수 호출 😱
animal1->makeSound(); // "동물이 소리를 냅니다" (멍멍이 아님!)
animal2->makeSound(); // "동물이 소리를 냅니다" (야옹이 아님!)
}해결책: virtual 키워드 ✨
// virtual을 사용한 경우 - 동적 바인딩
class Animal {
public:
virtual void makeSound() { // virtual 키워드!
cout << "동물이 소리를 냅니다" << endl;
}
virtual ~Animal() {} // 가상 소멸자 (중요!)
};
class Dog : public Animal {
public:
void makeSound() override { // override 키워드 (C++11)
cout << "멍멍! 🐕" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "야옹~ 🐱" << endl;
}
};
void testWithVirtual() {
cout << "=== virtual 사용 (동적 바인딩) ===" << endl;
Dog myDog;
Cat myCat;
Animal* animal1 = &myDog;
Animal* animal2 = &myCat;
// 올바른 동작! 실제 객체의 함수 호출 ✅
animal1->makeSound(); // "멍멍! 🐕"
animal2->makeSound(); // "야옹~ 🐱"
}🎮 실습: 도형 그리기 시스템
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
// 기본 도형 클래스
class Shape {
protected:
string name;
string color;
public:
Shape(string n, string c = "검정") : name(n), color(c) {
cout << "📐 도형 생성: " << name << endl;
}
virtual ~Shape() {
cout << "📐 도형 소멸: " << name << endl;
}
// 가상 함수들
virtual void draw() {
cout << color << "색 " << name << "을(를) 그립니다" << endl;
}
virtual double getArea() {
return 0.0; // 기본 도형은 면적이 없음
}
virtual double getPerimeter() {
return 0.0; // 기본 도형은 둘레가 없음
}
// 정보 출력
virtual void showInfo() {
cout << "\n=== " << name << " 정보 ===" << endl;
cout << "색상: " << color << endl;
cout << "면적: " << getArea() << endl;
cout << "둘레: " << getPerimeter() << endl;
}
};
// 원 클래스
class Circle : public Shape {
private:
double radius;
public:
Circle(double r, string c = "빨강")
: Shape("원", c), radius(r) {
cout << "⭕ 반지름 " << radius << "인 원 생성" << endl;
}
void draw() override {
cout << "⭕ " << color << "색 원을 그립니다 ";
cout << "(반지름: " << radius << ")" << endl;
}
double getArea() override {
return 3.14159 * radius * radius;
}
double getPerimeter() override {
return 2 * 3.14159 * radius;
}
};
// 사각형 클래스
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h, string c = "파랑")
: Shape("사각형", c), width(w), height(h) {
cout << "▭ " << width << "x" << height << " 사각형 생성" << endl;
}
void draw() override {
cout << "▭ " << color << "색 사각형을 그립니다 ";
cout << "(크기: " << width << "x" << height << ")" << endl;
}
double getArea() override {
return width * height;
}
double getPerimeter() override {
return 2 * (width + height);
}
};
// 삼각형 클래스
class Triangle : public Shape {
private:
double base;
double height;
double side1, side2, side3;
public:
Triangle(double b, double h, string c = "초록")
: Shape("삼각형", c), base(b), height(h) {
// 간단한 계산 (정확하지 않음, 예시용)
side1 = base;
side2 = sqrt(base*base/4 + height*height);
side3 = side2;
cout << "🔺 밑변 " << base << ", 높이 " << height << " 삼각형 생성" << endl;
}
void draw() override {
cout << "🔺 " << color << "색 삼각형을 그립니다 ";
cout << "(밑변: " << base << ", 높이: " << height << ")" << endl;
}
double getArea() override {
return 0.5 * base * height;
}
double getPerimeter() override {
return side1 + side2 + side3;
}
};
// 그림판 클래스 - 다형성 활용!
class Canvas {
private:
vector<Shape*> shapes;
public:
~Canvas() {
clear();
}
void addShape(Shape* shape) {
shapes.push_back(shape);
cout << "✅ 도형 추가 완료!" << endl;
}
// 모든 도형 그리기 - 다형성!
void drawAll() {
cout << "\n🎨 === 캔버스 그리기 ===" << endl;
for (Shape* shape : shapes) {
shape->draw(); // 각 도형의 draw() 호출!
}
}
// 전체 면적 계산
double getTotalArea() {
double total = 0;
for (Shape* shape : shapes) {
total += shape->getArea(); // 각 도형의 면적
}
return total;
}
// 모든 도형 정보
void showAllInfo() {
cout << "\n📊 === 도형 정보 ===" << endl;
for (Shape* shape : shapes) {
shape->showInfo();
}
cout << "\n총 도형 개수: " << shapes.size() << endl;
cout << "전체 면적 합: " << getTotalArea() << endl;
}
void clear() {
for (Shape* shape : shapes) {
delete shape;
}
shapes.clear();
cout << "🗑️ 캔버스 초기화" << endl;
}
};
int main() {
cout << "=== 도형 그리기 시스템 ===" << endl;
Canvas myCanvas;
// 다양한 도형 추가
myCanvas.addShape(new Circle(5, "빨강"));
myCanvas.addShape(new Rectangle(10, 5, "파랑"));
myCanvas.addShape(new Triangle(8, 6, "초록"));
myCanvas.addShape(new Circle(3, "노랑"));
// 모든 도형 그리기
myCanvas.drawAll();
// 정보 출력
myCanvas.showAllInfo();
return 0;
}📚 가상 함수 테이블 (VTable)
VTable의 동작 원리 🔧
class Base {
virtual void func1(); // VTable[0]
virtual void func2(); // VTable[1]
void func3(); // VTable에 없음 (non-virtual)
};
class Derived : public Base {
void func1() override; // VTable[0] 덮어씀
void func2() override; // VTable[1] 덮어씀
void func3(); // 그냥 숨김 (hiding)
};
// 각 객체는 VTable 포인터를 가짐
// Base 객체 → Base VTable
// Derived 객체 → Derived VTable🎯 가상 소멸자의 중요성
#include <iostream>
using namespace std;
// 잘못된 예 - 가상 소멸자가 없음 ❌
class BadBase {
public:
~BadBase() { // virtual이 없음!
cout << "BadBase 소멸자" << endl;
}
};
class BadDerived : public BadBase {
int* data;
public:
BadDerived() {
data = new int[100];
cout << "BadDerived 생성자" << endl;
}
~BadDerived() {
delete[] data;
cout << "BadDerived 소멸자" << endl;
}
};
// 올바른 예 - 가상 소멸자 사용 ✅
class GoodBase {
public:
virtual ~GoodBase() { // virtual 소멸자!
cout << "GoodBase 소멸자" << endl;
}
};
class GoodDerived : public GoodBase {
int* data;
public:
GoodDerived() {
data = new int[100];
cout << "GoodDerived 생성자" << endl;
}
~GoodDerived() override {
delete[] data;
cout << "GoodDerived 소멸자" << endl;
}
};
void testDestructors() {
cout << "=== 가상 소멸자 테스트 ===" << endl;
cout << "\n--- virtual 없는 경우 (메모리 누수!) ---" << endl;
BadBase* bad = new BadDerived();
delete bad; // BadDerived 소멸자 호출 안됨! 😱
cout << "\n--- virtual 있는 경우 (안전!) ---" << endl;
GoodBase* good = new GoodDerived();
delete good; // 모든 소멸자 올바르게 호출! ✅
}🔄 오버로딩 vs 오버라이딩
| 구분 | 오버로딩 (Overloading) | 오버라이딩 (Overriding) |
|---|---|---|
| 위치 | 같은 클래스 내 | 상속 관계 |
| 함수명 | 같음 | 같음 |
| 매개변수 | 다름 | 같음 |
| 반환 타입 | 다를 수 있음 | 같아야 함 |
| virtual | 불필요 | 필요 |
class Example {
public:
// 오버로딩 - 같은 이름, 다른 매개변수
void print(int x) { }
void print(double x) { }
void print(string x) { }
// 오버라이딩 준비
virtual void display() { }
};
class Derived : public Example {
public:
// 오버라이딩 - 부모 함수 재정의
void display() override { }
};💡 핵심 정리
- 가상 함수: virtual 키워드로 선언된 함수
- 동적 바인딩: 실행 시간에 호출할 함수 결정
- 정적 바인딩: 컴파일 시간에 호출할 함수 결정
- 오버라이딩: 부모의 가상 함수를 자식이 재정의
- 가상 소멸자: 다형성 사용 시 필수!
✅ 실습 체크리스트
🚀 다음 시간 예고
다음 시간에는 추상 클래스와 인터페이스에 대해 알아볼 거예요!
- 순수 가상 함수
- 추상 클래스 설계
- 인터페이스 패턴
“virtual로 진정한 다형성을 실현하세요! 🎭”
Last updated on