Programming Languages/C++

다형성(Polymorphism)

newclass 2025. 3. 28. 00:06

5.6 다형성(Polymorphism)

5.6.1 함수 오버라이딩(Function Overriding)

파생 클래스에서 기본 클래스의 메서드를 재정의하는 것:

#include <iostream>
#include <string>

class Shape {
protected:
    std::string name;
    
public:
    Shape(const std::string& n) : name(n) {}
    
    void display() const {
        std::cout << "도형: " << name << std::endl;
    }
    
    double area() const {
        std::cout << "기본 도형의 면적 계산" << std::endl;
        return 0.0;
    }
};

class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(const std::string& n, double r) : Shape(n), radius(r) {}
    
    // display() 메서드 오버라이딩
    void display() const {
        std::cout << "원: " << name << ", 반지름: " << radius << std::endl;
    }
    
    // area() 메서드 오버라이딩
    double area() const {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(const std::string& n, double w, double h) 
        : Shape(n), width(w), height(h) {}
    
    // display() 메서드 오버라이딩
    void display() const {
        std::cout << "직사각형: " << name << ", 가로: " << width 
                  << ", 세로: " << height << std::endl;
    }
    
    // area() 메서드 오버라이딩
    double area() const {
        return width * height;
    }
};

int main() {
    Shape shape("일반 도형");
    Circle circle("원A", 5.0);
    Rectangle rect("직사각형B", 4.0, 6.0);
    
    shape.display();
    circle.display();
    rect.display();
    
    std::cout << "일반 도형 면적: " << shape.area() << std::endl;
    std::cout << "원 면적: " << circle.area() << std::endl;
    std::cout << "직사각형 면적: " << rect.area() << std::endl;
    
    return 0;
}

5.6.2 가상 함수(Virtual Functions)와 동적 바인딩

가상 함수는 런타임에 적절한 함수 버전을 결정하는 다형성의 핵심입니다:

#include <iostream>
#include <string>
#include <vector>

class Shape {
protected:
    std::string name;
    
public:
    Shape(const std::string& n) : name(n) {}
    
    // 가상 함수
    virtual void display() const {
        std::cout << "도형: " << name << std::endl;
    }
    
    // 가상 함수
    virtual double area() const {
        return 0.0;
    }
};

class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(const std::string& n, double r) : Shape(n), radius(r) {}
    
    // 가상 함수 오버라이딩
    void display() const override {
        std::cout << "원: " << name << ", 반지름: " << radius << std::endl;
    }
    
    // 가상 함수 오버라이딩
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(const std::string& n, double w, double h) 
        : Shape(n), width(w), height(h) {}
    
    // 가상 함수 오버라이딩
    void display() const override {
        std::cout << "직사각형: " << name << ", 가로: " << width 
                  << ", 세로: " << height << std::endl;
    }
    
    // 가상 함수 오버라이딩
    double area() const override {
        return width * height;
    }
};

int main() {
    // 기본 클래스 포인터로 파생 클래스 객체 가리키기
    Shape* shapes[3];
    shapes[0] = new Shape("일반 도형");
    shapes[1] = new Circle("원A", 5.0);
    shapes[2] = new Rectangle("직사각형B", 4.0, 6.0);
    
    // 동적 바인딩: 실제 객체 타입에 따라 적절한 함수 호출
    for (int i = 0; i < 3; i++) {
        shapes[i]->display();  // 가상 함수 호출
        std::cout << "면적: " << shapes[i]->area() << std::endl;
    }
    
    // 메모리 해제
    for (int i = 0; i < 3; i++) {
        delete shapes[i];
    }
    
    return 0;
}

가상 함수의 특징:

  • virtual 키워드로 선언
  • 파생 클래스에서 override 키워드(C++11)로 명시적 재정의 가능
  • 동적 바인딩을 통해 런타임에 올바른 함수 버전 호출

5.6.3 순수 가상 함수(Pure Virtual Functions)와 추상 클래스(Abstract Classes)

순수 가상 함수는 구현이 없는 가상 함수로, 이를 포함하는 클래스는 추상 클래스가 됩니다:

#include <iostream>
#include <string>
#include <vector>

// 추상 기본 클래스
class Shape {
protected:
    std::string name;
    
public:
    Shape(const std::string& n) : name(n) {}
    
    // 일반 가상 함수
    virtual void display() const {
        std::cout << "도형: " << name << std::endl;
    }
    
    // 순수 가상 함수 (구현 없음)
    virtual double area() const = 0;  // '= 0'으로 순수 가상 함수 지정
    
    // 가상 소멸자
    virtual ~Shape() {
        std::cout << name << " 소멸" << std::endl;
    }
};

// 구체 파생 클래스
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(const std::string& n, double r) : Shape(n), radius(r) {}
    
    void display() const override {
        std::cout << "원: " << name << ", 반지름: " << radius << std::endl;
    }
    
    // 순수 가상 함수 구현 필수
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

// 또 다른 구체 파생 클래스
class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(const std::string& n, double w, double h) 
        : Shape(n), width(w), height(h) {}
    
    void display() const override {
        std::cout << "직사각형: " << name << ", 가로: " << width 
                  << ", 세로: " << height << std::endl;
    }
    
    // 순수 가상 함수 구현 필수
    double area() const override {
        return width * height;
    }
};

int main() {
    // Shape shape("일반 도형");  // 오류: 추상 클래스는 인스턴스화 불가
    
    Circle circle("원A", 5.0);
    Rectangle rect("직사각형B", 4.0, 6.0);
    
    // 다형성 활용
    Shape* shapes[2];
    shapes[0] = &circle;
    shapes[1] = &rect;
    
    double totalArea = 0.0;
    
    for (int i = 0; i < 2; i++) {
        shapes[i]->display();
        double shapeArea = shapes[i]->area();
        std::cout << "면적: " << shapeArea << std::endl;
        totalArea += shapeArea;
    }
    
    std::cout << "총 면적: " << totalArea << std::endl;
    
    return 0;
}

추상 클래스의 특징:

  • 최소 하나 이상의 순수 가상 함수 포함
  • 직접 인스턴스화 불가능
  • 파생 클래스의 인터페이스 역할
  • 파생 클래스는 모든 순수 가상 함수를 구현해야 함

5.6.4 가상 소멸자(Virtual Destructors)

다형성을 사용할 때 기본 클래스 포인터로 파생 클래스 객체를 삭제할 경우, 가상 소멸자가 필요합니다:

#include <iostream>
#include <string>

class Base {
public:
    Base() {
        std::cout << "Base 생성자" << std::endl;
    }
    
    // 비가상 소멸자
    // ~Base() {
    //     std::cout << "Base 소멸자" << std::endl;
    // }
    
    // 가상 소멸자
    virtual ~Base() {
        std::cout << "Base 가상 소멸자" << std::endl;
    }
};

class Derived : public Base {
private:
    int* data;
    
public:
    Derived() : Base() {
        std::cout << "Derived 생성자" << std::endl;
        data = new int(100);  // 동적 메모리 할당
    }
    
    ~Derived() {
        std::cout << "Derived 소멸자" << std::endl;
        delete data;  // 동적 메모리 해제
    }
};

int main() {
    // 비가상 소멸자일 경우
    std::cout << "비가상 소멸자 문제 시연:" << std::endl;
    Base* ptr = new Derived();
    delete ptr;  // 가상 소멸자가 아니면 Derived의 소멸자 호출되지 않음 -> 메모리 누수
    
    return 0;
}

가상 소멸자의 중요성:

  • 기본 클래스 포인터로 파생 클래스 객체를 삭제할 때 적절한 소멸자 체인 보장
  • 메모리 누수 방지
  • 다형성을 사용하는 모든 기본 클래스에 가상 소멸자 선언 권장