Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
C++ 프로그래밍Unit 12: 상속과 다형성Topic 2: 가상 함수와 동적 바인딩

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