6장. 상속, 그리고 객체 지향 설계🕶

안녕하세요! 두두코딩 입니다 ✋
오늘은 Effective C++ 항목 37에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

바인딩 개념

바인딩 개념은 항목 36 에서 첫 단락에 정리해두었습니다. 해당 개념을 알고 포스팅을 보면 좀 더 도움이 됩니다.

정적 바인딩은 선행 바인딩 이라는 이름으로, 동적 바인딩은 지연 바인딩 이라는 이름으로 많이 알려져있다는 점을 기억하도록 합시다!

매개변수 기본값 바인딩

우리가 흔히, 함수에 매개변수의 일반적인 기본 값을 넣어두고 많이 사용한다. 일례로 아래와 같은 코드를 보자.

class Shape {
	public:
		enum ShapeColor { Red, Green, Blue };
		...
		// 매개변수 기본 값 설정
		void draw(ShapeColor color = Red) const;
}

위 코드와 같이, 함수에 전달받는 매개변수의 기본 값을 먼저 지정해두고, 전달되지 않은 값을 처리하는 방법을 많이 사용한다.

해당 매개변수 기본 값 같은 경우 정적바인딩일까? 동적바인딩일까?

매개변수의 기본 값은 정적바인딩을 수행한다. 따라서, 컴파일타임 혹은 링킹타임에 해당 값이 결정된다. 이 점을 알고 아래의 내용을 보도록 하자.

C++ 에서 상속받을 수 있는 함수는 비가상함수가상함수 2가지이다. 가상함수 같은 경우 재정의가 가능하지만 비가상함수 같은 경우 재정의는 절대 금물이라고 앞서 이야기 해왔다.

가상함수 같은 경우 동적바인딩으로 프로그래밍 실행시점에 결정되는 객체 내 함수를 호출하도록 해주는데, 정적바인딩으로 결정되는 매개변수 기본 값이 포함되어져 있다면 어떻게 될까?

아래의 예시를 통해 알아보자.

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 = Green) const;
		...
};

class Circle : public Shape {
	public:
		virtual void draw(ShapeColor color) const;
		...
};

int main() {
	// 정적 타입 Shape*로 결정됨.
	Shape* ps;
	Shape* pc = new Circle;
	Shape* pr = new Rectangle;
}

위의 코드를 한번 보자.

정적타입으로 매칭 되는 경우 프로그램 실행과 관계없이 매칭이 된다. 즉, Shape* 타입을 할당 받을 경우 정적바인딩으로 매칭 되는 부분은 Shape 타입 기준으로 매칭이 된다.

반면에, 가상함수와 같은 동적타입으로 진행되는 부분은 객체 기준으로 불리기 때문에 draw()는 객체 별로 다르게 불린다.

위 내용은 C++을 공부했다면 어느정도 가늠할 수 있는 부분이다.

여기서 중요한 부분은 동적타입으로 선언된 가상함수 draw() 내 기본 매개변수 타입이다.

int main() {
	Shape* pr = new Rectangle;

	// Rectangle의 draw..
	// But, Shape::Red가 적용됨.
	// Rectangle::draw(ShapeColor::Red);
	pr->draw();
}

위와 같이, 동적바인딩으로 인해 가상함수인 draw() 함수가 Shape 타입의 포인터임에도 불구하고, Rectangle::draw() 즉, 실존 객체가 가지고 있는 draw()를 호출한다. 다만, 매개변수 기본 값 같은 경우 Rectangle::draw 함수가 가지고 있는 ShapeColor::Green이 아니라 Shape::draw함수가 가지고 있는 ShapeColor::Red가 지정된다.

그 이유는 기본 매개변수 값은 “정적 바인딩”으로 컴파일 시점에 매칭 되기 때문이다.

기본 매개변수는 동일하게??

위의 문제점을 막기 위해서는 기본 매개변수 같은 경우 동일하게 작성해야된다.

아래의 코드를 보자.

class Shape {
	public:
		enum ShapeColor { Red, Green, Bule };

		virtual void draw(ShapeColor color = Red) const = 0;
		...
};

class Rectangle : public Shape {
	public:
		virtual void draw(ShapeColor color = Red) const;
};

위와 같이 기본 매개 변수 값은 동일 하게 가야한다는 점이다. 하지만, 프로그래머 입장에서 가장 마음에 안드는 부분이 발생한다. 이거 코드 중복아닌가? Shape 타입에 기본 매개변수 바뀌면 나머지 다 바꿔야하는데..?

이를 막기위해서는 설계를 변경하는 것을 추천한다.

우리는 앞서 NVI(non-virtual interface) 관용구를 배웠다. 해당 방법을 사용할 경우 파생 클래스에서 재정의 할 수 있는 가상함수를 private에 두고, public에 비가상 함수를 인터페이스로 열어 주는 방법이다.

class Shape {
	public:
		enum ShapeColor { Red, Green, Blue };

		void draw(ShapeColor color = Red) const
		{
			doDraw(color);
		}
		...

	private:
		virtual void doDraw(ShapeColor color) const;
		...
};

class Rectangle : public Shape {
	public:
		...
	private:
		// 기본 매개변수 없음.
		virtual void doDraw(ShapeColor color) const;
		...
};

비가상함수의 오버라이드는 절대 금지이기 때문에, 위와 같이 설계할 경우 기본 매개변수의 값의 중복을 제거하면서 설정 가능하다.