프로그래밍/C++

고급 객체 지향 프로그래밍: 다형성, 가상 함수, 추상 클래스

shimdh 2025. 2. 3. 10:37
728x90

1. 다형성(Polymorphism)이란?

1.1 다형성의 정의

다형성은 "여러 형태를 가질 수 있는 능력" 을 의미합니다. 객체 지향 프로그래밍에서 다형성은 동일한 인터페이스를 통해 다양한 데이터 타입의 객체를 처리할 수 있게 해줍니다. 예를 들어, 동일한 함수 호출이 다른 클래스의 객체에 대해 다른 동작을 수행할 수 있습니다.

1.2 다형성의 중요성

  • 코드 재사용성: 공통된 인터페이스를 사용하여 다양한 클래스를 쉽게 사용할 수 있습니다.
  • 유지보수 용이성: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있습니다.
  • 유연성: 다양한 객체를 동일한 방식으로 처리할 수 있어 프로그램의 유연성이 증가합니다.

1.3 다형성의 종류

다형성은 크게 두 가지로 나뉩니다:

  1. 컴파일 타임 다형성 (정적 바인딩): 함수 오버로딩과 연산자 오버로딩을 통해 구현됩니다.
  2. 런타임 다형성 (동적 바인딩): 가상 함수와 상속을 통해 구현됩니다.

2. C++에서의 다형성 구현

C++에서는 다형성을 두 가지 방식으로 구현할 수 있습니다: 컴파일 타임 다형성런타임 다형성.

2.1 컴파일 타임 다형성 (정적 바인딩)

컴파일 타임 다형성은 함수 오버로딩(Function Overloading)연산자 오버로딩(Operator Overloading) 을 통해 구현됩니다. 이는 컴파일 시점에 어떤 함수가 호출될지 결정됩니다.

예제: 함수 오버로딩

#include <iostream>
using namespace std;

class Print {
public:
    void show(int i) {
        cout << "Integer: " << i << endl;
    }
    void show(double d) {
        cout << "Double: " << d << endl;
    }
};

int main() {
    Print p;
    p.show(10);      // 출력: Integer: 10
    p.show(3.14);    // 출력: Double: 3.14
    return 0;
}
  • 설명: show 함수는 매개변수의 타입에 따라 다른 동작을 수행합니다. 이는 컴파일 시점에 결정되므로 정적 바인딩이라고 합니다.

예제: 연산자 오버로딩

#include <iostream>
using namespace std;

class Complex {
private:
    double real, imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    Complex operator + (const Complex& obj) {
        Complex res;
        res.real = real + obj.real;
        res.imag = imag + obj.imag;
        return res;
    }

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3.0, 4.0), c2(1.0, 2.0);
    Complex c3 = c1 + c2;  // 연산자 오버로딩 사용
    c3.display();  // 출력: 4 + 6i
    return 0;
}
  • 설명: + 연산자를 오버로딩하여 복소수 덧셈을 구현했습니다. 이는 컴파일 시점에 결정되므로 정적 바인딩입니다.

2.2 런타임 다형성 (동적 바인딩)

런타임 다형성은 상속(Inheritance)가상 함수(Virtual Function) 를 통해 구현됩니다. 이는 프로그램 실행 중에 어떤 함수가 호출될지 결정됩니다.

예제: 가상 함수를 이용한 다형성

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() {  // 가상 함수
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {  // 오버라이딩
        cout << "Dog barks" << endl;
    }
};

class Cat : public Animal {
public:
    void sound() override {  // 오버라이딩
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* animal;
    Dog dog;
    Cat cat;

    animal = &dog;
    animal->sound();  // 출력: Dog barks

    animal = &cat;
    animal->sound();  // 출력: Cat meows

    return 0;
}
  • 설명: Animal 클래스의 sound 함수는 가상 함수로 선언되었으며, DogCat 클래스에서 오버라이딩됩니다. 이는 런타임 시점에 실제 객체의 타입에 따라 적절한 함수가 호출됩니다.

3. 가상 함수와 순수 가상 함수

3.1 가상 함수(Virtual Function)

가상 함수는 기본 클래스에서 선언되고, 파생 클래스에서 재정의(오버라이드)될 수 있는 멤버 함수입니다. 이를 통해 런타임 다형성을 구현할 수 있습니다.

예제: 가상 함수

class Animal {
public:
    virtual void sound() {  // 가상 함수
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {  // 오버라이딩
        cout << "Dog barks" << endl;
    }
};

3.2 순수 가상 함수(Pure Virtual Function)

순수 가상 함수는 기본 클래스에서 선언만 되고, 파생 클래스에서 반드시 구현되어야 하는 함수입니다. 순수 가상 함수가 포함된 클래스는 추상 클래스(Abstract Class) 로, 인스턴스를 생성할 수 없습니다.

예제: 순수 가상 함수

class Shape {
public:
    virtual void draw() = 0;  // 순수 가상 함수
};

class Circle : public Shape {
public:
    void draw() override {  // 구현
        cout << "Drawing Circle" << endl;
    }
};

class Square : public Shape {
public:
    void draw() override {  // 구현
        cout << "Drawing Square" << endl;
    }
};

int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Square();

    shape1->draw();  // 출력: Drawing Circle
    shape2->draw();  // 출력: Drawing Square

    delete shape1;
    delete shape2;

    return 0;
}
  • 설명: Shape 클래스는 추상 클래스로, draw 함수를 순수 가상 함수로 선언했습니다. CircleSquare 클래스는 이를 반드시 구현해야 합니다.

4. 추상 클래스(Abstract Class)

4.1 추상 클래스의 정의

추상 클래스는 최소한 하나 이상의 순수 가상 함수를 포함하는 클래스로, 인스턴스를 생성할 수 없습니다. 주로 인터페이스를 제공하고, 파생 클래스에서 구체적인 기능을 구현하도록 강제합니다.

4.2 추상 클래스의 장점

  • 인터페이스 제공: 여러 파생 클래스가 공통된 인터페이스를 따르도록 강제합니다.
  • 다형성 지원: 다양한 형태의 객체를 동일한 방식으로 처리할 수 있습니다.
  • 코드 재사용성: 공통된 기능을 기본 클래스에 정의하고, 파생 클래스에서 확장할 수 있습니다.

예제: 추상 클래스 활용

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() = 0;  // 순수 가상 함수
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Circle drawn!" << endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        cout << "Rectangle drawn!" << endl;
    }
};

void render(Shape* shape) {
    shape->draw();  // 다형성을 이용하여 각 도형을 그립니다.
}

int main() {
    Circle circle;
    Rectangle rectangle;

    render(&circle);     // 출력: Circle drawn!
    render(&rectangle);  // 출력: Rectangle drawn!

    return 0;
}
  • 설명: Shape 클래스는 추상 클래스로, draw 함수를 순수 가상 함수로 선언했습니다. CircleRectangle 클래스는 이를 구현하며, render 함수는 다양한 도형을 동일한 방식으로 처리합니다.

5. 다형성과 추상 클래스의 실제 활용 사례

5.1 게임 개발에서의 활용

게임 개발에서는 다양한 캐릭터나 객체를 다형성을 통해 관리할 수 있습니다. 예를 들어, 모든 캐릭터가 Character라는 추상 클래스를 상속받고, 각 캐릭터는 attack 메서드를 오버라이드하여 자신만의 공격 방식을 구현할 수 있습니다.

class Character {
public:
    virtual void attack() = 0;  // 순수 가상 함수
};

class Warrior : public Character {
public:
    void attack() override {
        cout << "Warrior swings a sword!" << endl;
    }
};

class Mage : public Character {
public:
    void attack() override {
        cout << "Mage casts a fireball!" << endl;
    }
};

int main() {
    Character* characters[] = { new Warrior(), new Mage() };
    for (Character* character : characters) {
        character->attack();  // 각 캐릭터의 공격 방식 출력
    }
    return 0;
}

5.2 GUI 프레임워크에서의 활용

GUI 프레임워크에서는 다양한 위젯(버튼, 텍스트 박스 등)을 다형성을 통해 관리할 수 있습니다. 모든 위젯이 Widget이라는 추상 클래스를 상속받고, 각 위젯은 draw 메서드를 오버라이드하여 자신만의 그리기 방식을 구현할 수 있습니다.

class Widget {
public:
    virtual void draw() = 0;  // 순수 가상 함수
};

class Button : public Widget {
public:
    void draw() override {
        cout << "Button drawn!" << endl;
    }
};

class TextBox : public Widget {
public:
    void draw() override {
        cout << "TextBox drawn!" << endl;
    }
};

int main() {
    Widget* widgets[] = { new Button(), new TextBox() };
    for (Widget* widget : widgets) {
        widget->draw();  // 각 위젯의 그리기 방식 출력
    }
    return 0;
}

6. 다형성과 추상 클래스의 장단점

6.1 장점

  • 코드 재사용성: 공통된 인터페이스를 통해 다양한 클래스를 쉽게 사용할 수 있습니다.
  • 유지보수 용이성: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있습니다.
  • 유연성: 다양한 객체를 동일한 방식으로 처리할 수 있어 프로그램의 유연성이 증가합니다.

6.2 단점

  • 성능 오버헤드: 가상 함수는 런타임에 동적으로 바인딩되므로, 정적 바인딩에 비해 약간의 성능 오버헤드가 발생할 수 있습니다.
  • 복잡성 증가: 다형성과 추상 클래스를 과도하게 사용하면 코드의 복잡성이 증가할 수 있습니다.

7. 결론

다형성, 가상 함수, 추상 클래스는 객체 지향 프로그래밍의 핵심 개념으로, 코드의 재사용성과 유연성을 크게 향상시킵니다. C++에서 이러한 개념을 잘 이해하고 활용하면, 더욱 구조적이고 유지보수가 용이한 코드를 작성할 수 있습니다. 특히, 복잡한 시스템을 설계할 때 다형성과 추상 클래스를 적절히 활용하면 코드의 확장성과 유연성을 극대화할 수 있습니다.

728x90