C++ 다형성 이해하기: virtual 함수와 순수 가상 함수
다형성(polymorphism)이란 여러 형태를 가짐을 의미합니다. 일반적으로 다형성은 클래스 계층이 있고 상속으로 연결되어 있을 때 발생합니다.
C++ 다형성은 멤버 함수 호출 시 해당 함수를 호출하는 객체 타입에 따라 다른 함수가 실행됨을 의미합니다.
기반 클래스가 다른 두 클래스로 파생된 다음 예제를 살펴보겠습니다.
Live Demo
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) { width = a; height = b; }
int area() {
cout << "Parent class area :" << endl;
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle(int a = 0, int b = 0): Shape(a, b) { }
int area() {
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
class Triangle: public Shape {
public:
Triangle(int a = 0, int b = 0): Shape(a, b) { }
int area() {
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// Rectangle 주소 저장
shape = &rec;
// rectangle area 호출
shape->area();
// Triangle 주소 저장
shape = &tri;
// triangle area 호출
shape->area();
return 0;
}
위 코드를 컴파일하고 실행하면:
Parent class area :
Parent class area :
잘못된 출력이 나오는 이유는 area() 함수 호출이 컴파일러에 의해 기반 클래스에 정의된 버전으로 한 번 설정되기 때문입니다. 이를 정적 바인딩(static binding) 또는 정적 연결(static linkage), 조기 바인딩(early binding)이라 합니다. area() 함수가 프로그램 컴파일 중에 설정되기 때문입니다.
이제 프로그램에서 Shape 클래스의 area() 선언 앞에 virtual 키워드를 붙여 약간 수정해 보겠습니다.
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) { width = a; height = b; }
virtual int area() {
cout << "Parent class area :" << endl;
return 0;
}
};
이 약간의 수정 후 이전 예제 코드를 컴파일하고 실행하면:
Rectangle class area
Triangle class area
이번에는 컴파일러가 포인터의 타입이 아닌 내용을 봅니다. tri와 rec 클래스 객체의 주소가 *shape에 저장되어 있으므로 각각의 area() 함수가 호출됩니다.
보시는 바와 같이 각 자식 클래스는 area() 함수에 대한 별도의 구현을 가집니다. 이것이 다형성이 일반적으로 사용되는 방식입니다. 같은 이름, 심지어 같은 매개변수를 가진 함수가 다른 클래스에 있지만 구현이 다릅니다.
가상 함수(Virtual Function)
가상 함수는 virtual 키워드를 사용해 기반 클래스에서 선언된 함수입니다. 기반 클래스에서 가상 함수를 정의하고 파생 클래스에서 다른 버전을 정의하면, 컴파일러에게 이 함수에 대해 정적 연결을 원하지 않음을 알립니다.
우리가 원하는 것은 프로그램의 주어진 시점에서 호출될 함수 선택이 그것이 호출되는 객체의 종류에 기반하는 것입니다. 이러한 유형의 작업을 동적 연결(dynamic linkage) 또는 후기 바인딩(late binding)이라 부릅니다.
순수 가상 함수(Pure Virtual Functions)
기반 클래스에 가상 함수를 포함시키고 싶지만, 그 함수에 대해 기반 클래스에서 의미 있는 정의를 내릴 수 없고 파생 클래스에서 해당 클래스 객체에 맞게 재정의되길 원할 수 있습니다.
기반 클래스의 가상 함수 area()를 다음과 같이 변경할 수 있습니다.
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) { width = a; height = b; }
// 순수 가상 함수
virtual int area() = 0;
};
= 0은 컴파일러에게 함수가 본문이 없음을 알리며, 위 가상 함수는 순수 가상 함수(pure virtual function)라 불립니다.