- 상속받은 기본 매개변수 값은 절대로 재정의해서는 안된다. 기본 매개변수 값은 정적으로 바인딩되는 반면, 가상 함수는 동적으로 바인딩되기 때문이다.
상속받은 기본 매개변수 값 재정의 금지
C++에서 상속받을 수 있는 함수의 종류는 가상 함수와 비가상 함수로 두 가지이다.
가상 함수는 동적으로 바인딩되고 비가상 함수는 정적으로 바인딩된다.
(정적 바인딩은 선행 바인딩, 동적 바인딩은 지연 바인딩이라고도 불린다.)
또한, 기본 매개변수 값도 정적으로 바인딩된다.
class Shpae {
public:
enum ShapeColor { Red, Green, Blue };
virtual void draw(ShpaeColor color = Red) const = 0;
...
};
class Rectangle: public Shape {
public:
virtaul void draw(ShapeColor color = Green) const = 0;
...
};
class Circle: public Shape {
public:
virtual void draw(ShapeColor color) const;
...
};
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
ps, pc, pr 모두 Shape에 대한 포인터로 선언되어 있다. 즉, 객체의 정적 타입은 Shape이다.
객체의 동적 타입은 현재 그 객체가 진짜로 무엇이냐에 따라 결정되는 타입이다.
pc의 동적 타입은 Circle*이고, pr의 동적 타입은 Rectangle*이다. ps의 경우엔 동적 타입이 없다.
동적 타입은 프로그램 도중에 바뀔 수 있다.
ps = pc; // ps의 동적 타입은 Circle*이 된다
ps = pr; // ps의 동적 타입은 Rectangle*이 된다
가상 함수는 동적으로 바인딩된다. 호출된 객체의 동적 타입에 따라 어떤 함수가 호출될지 결정된다.
pc->draw(Shape::Red); // Circle::draw(Shape::Red)
pr->draw(Shape::Red); // Rectangle::draw(Shape::Red)
이때, 기본 매개변수 값이 설정된 가상 함수가 있다면 문제가 발생할 수 있다.
가상 함수는 동적으로 바인딩되지만 기본 매개변수는 정적으로 바인딩되기 때문이다.
파생 클래스에 정의된 가상 함수를 호출하면서 기본 클래스에 정의된 기본 매개변수 값을 사용해 버릴 수 있다.
pr->draw(); // Rectangle::draw(Shape::Red)
pr의 동적 타입이 Rectangle*이므로, 호출되는 가상 함수는 Rectangle::draw이다.
Rectangle::draw 함수에서는 기본 매개변수 값이 Green으로 되어 있다.
하지만 pr의 정적 타입은 Shape*이기 때문에, 지금 호출되는 가상 함수에 쓰이는 기본 매개변수 값을 Shape에서 가져온다.
그 결과, Shape 및 Rectangle 클래스 양쪽에서 선언된 것이 섞이게 된다.
이렇게 동작하는 이유는 런타임 효율 때문이다.
만약 함수의 기본 매개변수가 동적으로 바인딩된다면, 프로그램 실행 중 가상 함수의 기본 매개변수 값을 결정할 방법을 컴파일러 쪽에서 마련해야 한다.
컴파일 과정에서 결정하는 것보다 느리고 복잡할 것이다.
지금의 메커니즘은 속도 유지와 구현 간편성에 초점을 맞춘 결과이다.
기본 매개변수 값을 똑같이 적용한다면 다음과 같다.
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
virtual void draw(ShapeColor color = Red) const = 0;
...
};
class Rectangle: public Shape {
public:
virtual void draw(ShapeColor color = Red) const;
...
};
코드 중복이 일어난다. 또한, 의존성까지 걸리게 된다.
Shape 클래스에서 기본 매개변수 값이 변하면 파생 클래스는 모두 그 값을 바꿔야 한다.
다른 방법
이런 경우 상속 말고 다른 방법을 적용해도 된다.
비가상 인터페이스(NVI)를 쓰는 것이다.
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const
{
doDraw(color);
}
...
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const;
...
};
비가상 함수는 파생 클래스에서 오버라이드 할 수 없다.
이렇게 하면 draw의 color 매개변수에 대한 기본값을 Red로 고정시킬 수 있다.