Modern C++ Design — 단위전략 기반의 클래스
C++을 사용하는 프로젝트에 참여하게 되었다. C++은 언어적으로 객체개념이 반영되었고, 다형성을 위해 오버로딩, 오버라이딩 등을 지원하는 언어로 알고 있었다. C를 구조체와 함수 포인터 적극적으로 사용하여 객체지향 언어처럼 개발을 했기 때문에 C++도 자신이 있었다. 하지만, C++11은 새롭게 탄생하였다. 함수형 특성을 적극적으로 반영해 절차형과 함수형의 특성을 모두 갖춘 언어로 변했다. 템플릿 메타 프로그래밍(TMP) 기법을 공부하던 중 ‘제너릭 프로그래밍과 디자인 패턴을 적용한 Modern C++ Design’ 책을 추천받고, 공부한 내용을 남긴다.
유연한 다지인을 방해하는 다중상속
- 구조적 문제 : 다중상속은 클래스를 포개어 놓고, 각각의 멤버를 접근하기 위한 단순한 규칙만을 제공할 뿐이다. 이는 단순한 경우를 제외하고는 한계를 가진다. 원하는 기능을 하려면, 상속된 클래스 간의 동작을 면밀히 조율해 주어야 한다.
- 상태 처리의 문제 : 다양한 동작 요소들은 그 상태가 정확기 동기화 되어야 한다.
템플릿(Template)의 이점
템플릿은 프로그래머가 선택한 자료형에 따라 컴파일 타임에 코드를 생성한다.
일반화된 템플릿을 선언하고, 자료형에 따라 별도의 일을 처리하고 싶을 땐 특화된 템플릿(Specialized template)을 사용할 수 있다. 또한 다중 인자를 가지는 클래스 템플릿은 템플릿 부분 특화도 가능하다.
하지만, 템플릿에도 몇 가지 문제점이 있다.
- 클래스의 멤버 변수를 특화시킬 수 없으며, 오직 멤버 함수에 대에서만 가능하다.
- 클래스 템플릿을 구현할 때 각각의 멤버 함수에 대해 오직 하나의 기본 값만 구현 가능하다.
- 템플릿 인자가 여러개인 멤버함수는 특화가 불가능하다.
//템플릿 인자가 하나인 클래스 템플릿
template <class T> class
widget {
void Fun() { ...}
};//OK. 템플릿 인자가 하나이기 때문에 특화
template<> Widget<char>::Fun() {
...
}//템플릿 인자가 두 개인 클래스 템플릿
template<class T, class U> class Gadget {
void Fun() { ... }
}// ERROR. 템플릿 인자 하나만 특화시킬 수 없다.
template <class U> void Gadget<char, U>::Fun()
{
...
}
단위전략과 단위전략 클래스
— 용어설명 —
- 단위전략(policy)이란 매우 단순한 동작이나 구조만을 가지는 작은 클래스이다. 특정 문제를 해결하는 데 꼭 맞는 인터페이스를 갖도록 디자인 된다.
- 단위전략 클래스는 단위전략에 대한 구체적인 구현을 일컷는다.
- 단위전략 기반의 클래스 디자인(policy-based class design)이란 단위전략을 모아서 복합된 기능을 하는 새로운 클래스를 만들어 내는 디자인 방식을 말한다.
- 호스트·호스트 클래스는 하나 이상의 단위전략을 사용하는 클래스를 일컷는다.
예를 들어 Creator 단위전략을 정의해보자. Creator는 Create라는 멤버 함수를 가지고 있으며, 이 멤버 함수는 자료형 T의 포인터를 반환하는 함수이다. 의미상, Create 함수는 매 호출마다 새로운 T 형의 객체에 대한 포인터를 반환해 주며, 그 객체가 생성되는 방식은 단위전략의 구현 방법에 따라 달라진다. 이처럼 Creator 단위전략은 인터페이스만 정의하고 있으며 구체적인 구현은 단위전략 클래스에서 이뤄진다.
//new 연산자를 사용한 단위전략 클래스
template <class T> struct
OpNewCreator {
static T* Create() {
return new T;
}
};//malloc을 사용한 단위전략 클래스
template <class T> struct
OpMallocCreator {
static T* Create() {
void* buf = std::malloc(sizeof(T));
return new(buf) T;
}
};
Creator 단위전략을 사용하는 클래스 디자인
// 라이브러리 코드
template <class CreationPolicy> class
WidgetManager : public CreationPolicy {
...
};// 어플리케이션 코드
WidgetManager<OpNewCreator> MyWidgetMgr;
단위전략의 핵심은 MyWidgetMgr 객체를 생성할 때 new 연산자를 사용할 지, malloc 함수를 사용할지에 대한 선택은 사용자의 몫으로 남겨두었다는 점이다.
단위전략 클래스 간의 조합
단위전략의 유용성은 단위전략 클래스들을 조합하여 사용할 때 극대화 된다.
template
<
class T,
template<class> class CheckingPolicy,
template<class> class ThreadingModel
>
class SmartPtr;SmartPtr은 세 계의 템플릿 인자를 가진다. 첫 번째 인자는 포인터가 가리킬 자료형이고, 나머지 두 가지는 선택해야 할 단위전략을 나타냅니다. 이처럼 두 개의 단위 전략을 하나의 SmartPtr에서 사용할 수 있습니다.
클래스를 단위전략으로 분리해내기
- 클래스의 동작을 구성하는 데 필요한 결정 사항을 파악
- 한 가지 이상의 방법으로 수행될 수 있는 일면 클래스부터 단위전략으로 분리
- 서로 상호작용하지 않는 요소를 찾아 분리
책에서는 단위전략에 대해 어렵고 장황하게 설명을 했다. 하지만, 내가 이해한 단위전략은 스트래터지 패턴과 유사하다. 단, 템플릿을 사용하여 컴파일 타임에 적용시켰다.