1 단위전략과 단위전략 클래스
단위전략(policy)과 단위전략 클래스는 안전하고, 효과적이며, 커스터마이징이 용이한 디자인 요소를 만드는데 도움이 되는 개념이자, 단위전략은 클래스 인터페이스, 혹은 클래스 템플릿 인터페이스를 정의하게 된다. 인터페이스란 모름지기 내부 자료형의 정의, 멤버 함수 그리고 멤버변수중 어느 하나, 혹은 그 전부로 구성되어 있다.
단위전략은 traits와 많은 공통점을 가지고 있지만, 자료형보다는 그들의 동작에 대한 부분을 더 강조하고 있다는 점에서 traits와는 차이점을 가진다. 또한, 단위전략이 컴파일 시간에 작용하는 속성은 Strategy 디자인 패턴을 연상시키기도 한다.
예를 들어, 임의의 객체를 생성하는 단위전략을 정의해 보도록 하자. Creator 단위전략은 자료형 T라는 클래스 템플릿의 속성을 다음과 같이 규정하게 된다. 이 클래스 템플릿은 Create 라는 멤버 함수를 노출해야 합니다. Create 멤버 함수는 인자를 가지지 않으며, T에 대한 포인터를 반환하는 함수 다. 객체가 생성되는 정확한 방식은 단위전략의 구현방법에 따라 달라질수 있다.
Creator 단위전략을 구현하는 단위전략 클래스를 정의해 보도록 하자.
template <typename T> struct OpNewCreator { static T* Create() { return new T; } }; template <typename T> struct MallocCreator { static T* Create() { void* buf = std::malloc(sizeof(T)); if (!buf) return 0; return new (buf) T; } }; template <typename T> struct PrototypeCreator { PrototypeCreator(T* pObj = nullptr) : pPrototype_(pObj) {} T* Create() { return pPrototype_ ? pPrototype_->Clone() : nullptr; } T* GetPrototype() { return pPrototype_; } void SetPrototype(T* pObj) { pPrototype_ = pObj; } private: T* pPrototype_; };
하나의 단위전략이 주어졌을때 그 구현 방법에는 무수히 많은 경우의 수가 존재한다. 단위전략에 대한 구현물을 단위전략 클래스 라고 부른다. 단위전략 클래스는 단독으로 사용되기 보다는, 다른 클래스의 기반 클래스로 사용되거나, 또는 다른 클래스의 멤버로 포함되도록 디자인 된다.
단위전략 인터페이스의 중요한 특징은 고전적인 인터페이스(pure virtual 함수)와는 달리 그것이 다소 모호하게 정의된다는 점이다. 단위전략은 그 의미보다는 문법 자체에 더 초첨이 맞추어져 있다. 다시 말하면 Creator 단위전략은 클래스가 정확히 어떤 동작을 하는 함수를 구현해야 하느냐 보다는, 클래스를 구성하는 데 어떠한 문법적 구성이 올바른가를 말해 준다. 예를 들어 Creator 단위전락은 Create 함수가 static 이어야 하는지, 아니면 가상 함수여야 하는지의 문제에는 관여하지 않는다. 유일한 요구사항은 이 클래스 템플릿이 Create 멤버 함수를 정의하고 있다는 것 뿐이다. 또한, Create 멤버 함수가 새로운 객체 T의 포인터를 반환할 것이라는 것이다.
위에서 정의한 세가지 단위전략 클래스들은 그 구현 방법이 모두 다르며, 심지어 인터페이스도 조금은 다르다(PrototypeCreator에는 별도의 멤버 함수가 있다). 어쨌든, 세가지 모두 정해진 자료형을 반환하는 Create 함수를 가지면, 이로써 그들은 모두 Creator 단위전략을 구성하게 된다.
이제, Creator 단위전략을 사용하는 클래스를 디자인하는 방법을 살펴 보자. 이 클래스는 다음과 같이 위에서 정의된 세 가지 단위전략 클래스 중 하나를 상속받거나 또는 포함해야 한다.
//라이브러리 코드 template <typename CreationPolicy> class WidgetManager : public CreationPolicy { ... };
하나, 혹은 그 이상의 단위전략을 사용하는 클래스를 호스트 내지는 호스트 클래스 라고 부른다. WidgetManager는 하나의 단위전략을 상속받는 호스트 클래스다. 호스트는 하나의 복합 단위 내에 여려 단위전략의 구조와 동작을 한데 모아야 하는 책임이 있다.
WidgetManager 템플릿의 인스턴스를 만들 때에는 클라이언트 프로그래머가 직접 필요한 전략을 템플릿 인자로 넘겨주어야 한다.
typedef WidgetManager<OpNewCreator<Widget>> MyWidgetMgr;
어떤 Creator 단위전략을 선택하는냐의 문제는 WidgetManager 사용자 자신의 몫이다. WidgetManager 자신의 디자인을 통하여, WidgetManager는 사용자가 그 기능에 대한 특정 부분을 마음대로 구성할 수 있는 효과적인 방법을 제공하게 된다.
이것이 바로 단위전략 기반의 클래스 디자인의 핵심이다.
1.1 템플릿 템플릿 인자를 통한 단위전략 클래스의 구현
단위전략의 템플릿 인자를 선택하는 것은 사용자의 몫이다. 사용자가 반드시 OpNewCreator의 템플릿 인자를 명시적으로 넘겨주어야 한다는 것은 사실 좀 불편한 일이다. 일반적으로, 호스트 클래스는 단위전략 클래스의 템플릿 인자를 알고 있거나, 또는 쉽게 추론해 낼 수 있다. 위의 예에서는, WidgetManager가 항상 Widget 형의 객체를 관리하게 된다. 따라서 Widget의 인스턴스를 만들 때 사용자가 직접 OpNewCreator의 템플릿 인자를 명시하도록 하는것은 잠재적인 위험 요소가 될 수 있다.
이러한 경우에, 라이브러리 코드는 템플릿 템플릿 인자를 사용하여 단위전략을 구성할 수 있다.
//라이브러리 코드 template <template <typename Created> typename CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { ... };
Created 심볼은 WidgetManager가 아니라 CreationPolicy를 위한 형식적인 인자에 불과하므로 생략해 버려도 상관없다.
애플리케이션 코드에서는 이제 WidgetManager의 인스턴스를 만들 때에 오로지 템플릿의 이름만을 제공하면 된다.
//애플리케이션 코드 typedef WidgetManager<OpNewCreator> MyWidgetMgr;
단위전략 클래스에서 템플릿 템플릿 인자를 사용하는 것은 단지 그 편리성의 이유 때문만은 아니다. 때때로 그것은 다음과 같은 이유로 필수적인 요소가 된다. 호스트 클래스는 서로 다른 자료형을 기준으로 자신을 구체화시킬 필요가 있으므로, 템플릿에 대한 접근이 필요한 것이다. 예를 들어, WidgetManager가 자신과 동일한 단위전략을 사용해서 Gaget 형의 객체를 생성한다고 가정해 보자 그러면 다음과 같은 코드가 가능하다.
//라이브러리 코드 template <template <typename> typename CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { ... void DoSomething() { Gadget* p = CreationPolicy<Gadget>.Create(); ... } };
다음과 같이 가장 일반적으로 사용되는 단위전략을 템플릿의 기본 인자로 정해 두는 것도 필요할 것이다.
template <template <typename> typename CreationPolicy = OpNewCreator> class WidgetManager ...
단위전략이 단순한 가상 함수(virtual function) 와는 상당히 다르다는 점에 주의하라. 가상 함수는 물론 비슷한 효과를 약속해 주긴 한다. 클래스 작성자는 기본적으로 가상 함수를 통해 높은 레벨의 함수를 정의하고, 상용자가 이것들을 오버라이드 하여 사용하도록 디자인할 수 있을 것이다. 하지만 단위전략의 경우에는 자료형을 컴파일 타임에 미리 파악할 수 있으므로 스태틱 바인딩(가상함수의 dynamic binding과 반대되는 개념)이 가능하다. 이것은 디자인을 구성하는 데 필수적인 요소가 될 수 있다. 지금까지의 디자인이란 것이 사실 실행 전에 어떤 자료형이 다른 자료형과 어떻게 상호작용하며, 사용자가 할 수 있는 일은 무엇이고, 또한 사용자가 할 수 없는 일은 무엇인지를 말해조는 규칙으로 가득차 있지 않았는가? 이제 단위전략을 사용하면, 몇가지 간단한 선택을 조합하는 것만으로 새로운 디자인을 창조해 낼 수 있으며, 자료형에 따른 안정성까지도 보장받을 수 있다. 더 나아가, 호스트 클래스와 단위전략 간의 연결이 컴파일 타임에 이루어지기 때문에 일일이 수작업으로 최적화시킨 코드에 뒤지지 않는, 단단하고도 효율적인 코드가 만들어지게 된다.
요약
단위전략의 메커니즘은 템플릿과 다중 상속을 병행한다느 것에 근거하고 있다. 단위전략을 사용하는 호스트 클래스는 다수의 템플릿 인자를 가지는 또 다른 템플릿이며, 이 때 각 템플릿 인자는 그것이 사용하는 단위전략을 가리키게 된다. 호스트 클래스는 자신이 선택한 단위전략들을 통해 그 기능을 간잡화시키게 되며, 각 단위전략들을 서로 밀접하게 응집시켜 주는 하나의 그릇으로써 작용하게 된다.
'프로그래밍' 카테고리의 다른 글
[Modern C++ Design] 단위전략 기반의 클래스 디자인(2) (0) | 2015.06.19 |
---|---|
std::unique_ptr 의 custom deleter 를 람다식 으로 지정하기 (0) | 2015.04.27 |
C++용 Microsoft 단위 테스트 프레임워크를 사용하여 C/C++용 단위 테스트 작성 (0) | 2015.04.18 |
[스크랩]C++11: unique_ptr (0) | 2013.10.04 |
[스크랩]tr1::shared_ptr (0) | 2013.09.18 |