컴파일 타임 vs 런타임 다형성
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 56번째.
다형성은 "여러 형태"를 의미하며, 객체를 일관된 방식으로 다루면서도 서로 다르게 동작할 수 있도록 하는 OOP의 핵심 개념입니다. C++는 두 가지 서로 다른 유형의 다형성을 지원하며, 각 유형은 프로그램 실행의 서로 다른 단계에서 해결됩니다.
컴파일 시간 다형성(정적 다형성이라고도 함)은 프로그램이 실행되기 전에 컴파일러에 의해 결정됩니다. 컴파일러는 함수 시그니처를 기반으로 정확히 어떤 함수를 호출할지 결정합니다. 여기에는 함수 오버로딩과 템플릿이 포함됩니다.
void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }
print(5); // 컴파일러가 print(int)를 선택합니다
print(3.14); // 컴파일러가 print(double)를 선택합니다런타임 다형성(동적 다형성이라고도 함)은 프로그램이 실행되는 동안 결정됩니다. 어떤 함수를 호출할지에 대한 결정은 포인터나 참조의 형식이 아니라 실제 객체 형식에 따라 달라집니다. 이는 가상 함수를 통해 구현됩니다.
class Shape {
public:
virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing circle" << std::endl; }
};
Shape* s = new Circle();
s->draw(); // 런타임에 결정됨: "Drawing circle"핵심적인 절충점은 다음과 같습니다. 컴파일 시간 다형성은 컴파일 중에 결정이 이루어지므로 런타임 오버헤드가 전혀 없는 반면, 런타임 다형성은 약간의 비용(vtable 조회)을 추가하지만 실행될 때까지 유형을 알 수 없는 객체를 더욱 유연하게 다룰 수 있도록 합니다.
챌린지
쉬움두 가지 다형성 유형을 나란히 보여 주는 계산기 시스템을 만들어 보겠습니다. 함수 오버로딩을 통해 컴파일 시간 다형성이 서로 다른 입력 유형을 처리하고, 런타임 다형성을 통해 서로 다른 calculation 전략을 동적으로 교체할 수 있는 시스템을 만들게 됩니다.
코드를 세 개의 파일로 구성합니다.
Calculator.h: 모든 calculation 전략을 나타내는 baseCalculatorclass를 define합니다.int를 반환하고Base calculation: <a> ? <b>를 출력하는 virtualcalculate(int a, int b)method (0을 반환)- virtual destructor
Operations.h: calculation 동작을 재정의하는 두 개의 Derived calculator class를 define합니다.Adder:calculate()를 override하여Adding: <a> + <b>를 출력하고 합을 반환Multiplier:calculate()를 override하여Multiplying: <a> * <b>를 출력하고 곱을 반환
overridekeyword를 사용해야 합니다.main.cpp: 두 다형성 유형을 모두 보여 주는 시스템을 Create합니다. 두 개의 integer inputs를 읽습니다(각각 별도의 줄에 입력).먼저, 오버로드된
display()functions 세 개를 Create하여 컴파일 시간 다형성을 보여 줍니다.display(int x)는Integer value: <x>를 출력합니다.display(double x)는Double value: <x>를 출력합니다.display(const std::string& x)는String value: <x>를 출력합니다.
그런 다음 base
Calculator,Adder,Multiplier를 포함하는Calculator*pointer array를 Create하여 런타임 다형성을 보여 줍니다. 각 항목을 Loop하면서 입력 값으로calculate()을 call하고, 각 calculation 후 result를 출력합니다.출력은 다음과 같은 구조로 작성합니다.
=== Compile-Time Polymorphism === <display outputs for int, double, string> === Runtime Polymorphism === <calculate outputs with results>컴파일 시간 섹션에서는 첫 번째 입력을 integer로, 그다음에는 .5를 더한 동일한 값으로, 마지막에는 문자열 "Result"로
display()를 call합니다. 작업이 끝나면 dynamically allocated calculators를 Clean up합니다.
예를 들어 inputs가 10과 3인 경우:
=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result
=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30컴파일러가 argument 유형에 따라 올바른 display() overload를 선택하는 반면(컴파일 시간 결정), 올바른 calculate() method는 vtable 메커니즘을 통해 런타임에 실제 object 유형에 따라 결정된다는 점에 주목하세요.
직접 해보기
#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"
// TODO: 세 개의 오버로드된 display() 함수를 생성하세요:
// 1. display(int x) - prints "Integer value: <x>"
// 2. display(double x) - "Double value: <x>"를 출력합니다
// 3. display(const std::string& x) - "String value: <x>"를 출력합니다
int main() {
// 두 개의 정수 입력을 읽습니다
int a, b;
std::cin >> a;
std::cin >> b;
// === Compile-Time Polymorphism ===
std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
// TODO: 다음으로 display()를 호출하세요:
// - a를 정수로
// - a를 double로 (0.5를 더함)
// - 문자열 "Result"
std::cout << std::endl;
// === Runtime Polymorphism ===
std::cout << "=== Runtime Polymorphism ===" << std::endl;
// TODO: 3개의 요소를 가진 Calculator* 포인터 배열을 생성하세요:
// - a base Calculator
// - Adder
// - Multiplier
// TODO: 배열을 순회하며 각각에 대해 calculate(a, b)를 호출하고,
// 그리고 각 계산 후에 "Result: <return_value>"를 출력
// TODO: 동적으로 할당된 메모리 정리
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러