policy based class design
DESCRIPTION
Modern C++ Design, Chapter 01: Policy based Class DesignTRANSCRIPT
단위전략policy 기반의 클래스 디자인
• 매우 단순한 동작이나 구조만을 갖는 작은 클래스(policy)들을 모아
서 복합된 기능을 하는 새로운 클래스를 만들어내는 디자인 방식
• 단위전략들은 서로 간에 연동되거나 조합되어 사용될 수 있으므로
다양한 결과물을 얻어낼 수 있음
policy classes host class
소프트웨어 디자인의 다양성
• 디자인 문제에 있어서 올바른 해법은?
• 단일 vs 다중 스레드, 안정성 vs 성능, 감시자 vs 콜백, 가상 함수 vs 템플릿
• 소프트웨어 시스템 디자인은 문제를 해결하는 다양한 해법들 중 올
바른 방법을 끊임없이 선택하는 과정
• 유연하고, 안전하고, 사용자가 쉽게 조절할 수 있고
적절한 양의 코드로 다양성의 재앙과 맞서 싸우기 위한 방법은?
Do-It-All 인터페이스의 문제점
• 모든 기능에 대한 인터페이스를 구현(Do-It-All 인터페이스)할 경우,
• 기능이 런타임에 수행되므로 컴파일 시점에 검사할 수 있는 내용이 축소됨
문법적으로는 옳지만 의미상으로는 옳지 않은 코드가 만들어질 수 있음
• 서로 다른 디자인 요소들을 작은 별개의 클래스로 구현할 경우,
• 지나치게 다양한 선택의 조합이 난립할 수 있음
• 미리 규정된 라이브러리 제한 조건보다는
사용자가 만든 디자인 스스로의 제한 조건을 가지도록 설계해야 함
• 프로그래머가 선택하고 커스터마이징을 할 수 있도록 설계해야 함
다중 상속이 해결책이 될 수 있을까요?
• 잘 디자인된 기반 클래스 여러 개를 상속받는 것으로 될까?
• 기반 클래스의 조합은 가능하지만 부족한 부분이 있음
• 다중 상속은 단지 기반 클래스들을 포개어놓고 멤버를 접근하기 위한 기능
이므로 상속된 컴포넌트들을 제어하거나 취합하는 것은 직접 해야 함
• 기반 클래스들은 자신이 모르는 클래스에 대한 작업을 해야 함
• 기반 클래스가 상태를 가질 경우 가상 상속을 이용해야만 함
템플릿의 이점
• 선택한 자료형에 따라 컴파일 시점에 코드를 컴파일러가 생성해줌
• 부분 특화Specialization를 통해 사용자가 특화된 기능 추가가 가능
• 다중 인자 클래스 템플릿의 부분 인자 특화로 인한 확장성
• 단점
• 멤버 변수 수준의 template 적용은 불가능
• 멤버 함수의 부분 특화는 불가능
• 다중 상속과 템플릿의 병용하여 라이브러리를 작성함
template <class T, class U> class SmartPtr;template <class U> class SmartPtr<Widget, U> {
// some codes};
template <class T> class Widget {void Fun() { /* codes */ }
};template <> void Widget<char>::Fun() {
// some specialized codes}
template <class T, class U> class Gadget {void Fun() { /* codes */ }
};template <class U>void Gadget<char, U>::Fun() {
// compile error!}
template <> void Gadget<char, int>::Fun() {// it’s ok!
}
템플릿 인자를 부분 특수화할 수 있다.
완전 특수화를 하여 멤버 함수를 정의할 수는 있지만,
멤버 함수는 부분 특수화를 할 수 없다.
단위전략과 단위전략 클래스
• 단위전략 클래스: 단위전략에 대한 구현물
• 명시적 인터페이스 구현이 아니라 template에 의해 모호하게 정의됨
• 호스트 클래스: 단위전략을 사용하는 클래스
• 특정 부분을 마음대로 구성할 수 있는 효과적인 방법을 제공
template <class T> struct OpNewCreator {static T* Create() { return T; }
};
template <class CreationPolicy>class WidgetManager : public CreationPolicy {};
typedef WidgetManager<OpNewCreator<Widget>> MyWidgetMgr;
template <class T> struct MallocCreator {static T* Create() {
void* buf = std::malloc(sizeof(T));return buf == nullptr ? nullptr : new (buf)T;
}};
template <class 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_;
};
템플릿 인자로 합성해서 사용한다.
생성에 대한 각기 다른 단위전략을 구현하고,
모두 Create()라는 함수를 갖지만 명시적 interface는 없다.
PrototypeCreator처럼 상태를 가질 수 있으므로CreationPolicy를 상속받아 구현한다.
템플릿 템플릿 인자를 통한 구현
• 템플릿 템플릿 인자를 사용한 단위전략 구성
• 보다 유연한 코드를 작성할 수 있게 해줌
• 사용자가 보다 애플리케이션에 정확히 들어맞는 고유의 생성 전략
을 제공할 수 있음
template <template <class Created> class CreationPolicy>class WidgetManager : public CreationPolicy<Widget> {
void DoSomething() {Gadget* pW = CreationPolicy<Gadget>().Create();
}};
typedef WidgetManager<OpNewCreator> MyWidgetMgr;
template <template <class> class CreationPolicy = OpNewCreator>class WidgetManager;
템플릿 템플릿 인자로 CreationPolicy를 받는다.
보다 편리하게 사용할 수 있도록 기본 단위전략을 지정해 줄 수 있다.
이 지점에서 Created는 의미가 없으므로 생략 가능하다.
생성 시 Type을 지정하지만 상속 시의 Type은 Widget이므로,PrototypeCreator일 경우 Gadget은 Widget을 상속받아야 한다.
템플릿 멤버 함수를 통한 구현
• 구형 컴파일러에서도 잘 동작함
• 템플릿 클래스에 비해 논의, 정의, 구현, 사용법이 까다로움
struct OpNewCreator {template <class T>static T* Create() {
return new T;}
};
단위전략 인터페이스의 보강
• 라이브러리가 아닌 사용자 자신이 어떤 단위전략 클래스를 사용
• 기능을 지원하지 않는 다른 단위전략 클래스를 사용하면 컴파일러
가 경고
typedef WidgetManager<PrototypeCreator> MyWidgetManager;
// ...Widget* prototype = CreatePrototype();MyWidgetManager mgr;mgr.SetPrototype(prototype);// ... PrototypeCreator 단위전략 함수를 갖게되어 인터페이스가 보강됨
단위전략 클래스의 소멸자
• 단위전략 클래스로 강제 casting 후 소멸자 호출 막기 위해 소멸자
를 protected로 변경
• 가상 소멸자를 정의하는 것은 불필요한 부담을 야기
typedef WidgetManager<PrototypeCreator> MyWidgetManager;
MyWidgetManager mgr;PrototypeCreator<Widget>* creator = &mgr;delete creator; // ???
template <class T>struct PrototypeCreator {
// ...protected:
~PrototypeCreator() {}};
불완전한 구체화를 통한 부가기능
• 템플릿에서 전혀 사용되지 않는 멤버 함수는 구체화 시 무시됨
• 컴파일러에 따라 다르지만 심볼 유효성 등 문맥적 검사는 하지 않음
template <template <class> class CreationPolicy>class WidgetManager : public CreationPolicy<Widget> {
void SwitchPrototype(Widget* newPrototype) {CreationPolicy<Widget>& myPolicy = *this;delete myPolicy.GetPrototype();myPolicy.SetPrototype(newPrototype);
}};
OpNewCreator를 쓰면서 SwitchPrototype()을 부르지 않으면 OKPrototypeCreator를 쓸 경우 SwitchPrototype()을 불러도 OK
단위전략 클래스 간의 조합
• 호스트 클래스는 여러 단위 전략을 조합하여 다양한 동작을 정의할
수 있음
template <class T,template <class> class CheckingPolicy,template <class> class ThreadingModel
>class SmartPtr;
typedef SmartPtr<Widget, NoChecking, SingleThreaded> WidgetPtr;typedef SmartPtr<Widget, EnforceNotNull, SingleThreaded> SafeWidgetPtr;
template <class T> struct NoChecking {static void Check(T*) {}
};template <class T> struct EnforceNotNull {
static void Check(T* ptr) {if (!ptr)
throw NullPointerException();}
};
template <class T,template <class> class CheckingPolicy,template <class> class ThreadingModel
>class SmartPtr
: public CheckingPolicy<T>, public ThreadingModel<SmartPtr<T, CheckingPolicy, ThreadingModel>> {
public:T* operator -> () {
typename ThreadingModel<SmartPtr>::Lock guard(*this);CheckingPolicy<T>::Check(pointee_);return pointee_;
}private:
T* pointee_;};
적절한 단위전략을 선택하여 어떠한 동작을 할지 결정할 수 있다.
단위전략 클래스를 통한 커스터마이징
• 단위전략과 상속을 사용하여 클래스의 구조를 변경
template <class T>class DefaultSmartPtrStorage {public:
typedef T* PointerType;protected:
PointerType GetPointer() { return ptr_; }void SetPointer(PointerType ptr) { ptr_ = ptr; }
private:PointerType ptr_;
};template <
class T,template <class> class Storage = DefaultSmartPtrStorage
>class SmartPtr : public Storage<T> {};
T*가 아닌 type에 대해서도 사용하기 위해 Storage 추상화
Storage에 필요한 저장 구조를 포함하기 위해 상속
호환/비호환 단위전략
• 호환 가능한 단위전략을 위해 단위전략 간의 형변환 구현(복사 생성자
나 형변환 연산자) 후 단위전략 단위로 복사를 하도록 구현
template <class T,template <class> class CheckingPolicy>
class SmartPtr : public CheckingPolicy<T> {template <
class T1,template <class> clsas CP1>
SmartPtr(const SmartPtr<T1, CP1>& other): pointee_(other.pointee_), CheckingPolicy<T>(other)
{}};
다른 policy를 갖는 SmartPtr을 인자로 받아서,
policy의 복사 생성자를 불러준다.때문에 policy간의 복사 생성자가 구현되어 있을 때만 허용된다.
클래스를 단위전략으로 분리해 내기
• 극단적인 경우 호스트 클래스는 단위전략의 집합체로만 표현됨
• 이 경우 템플릿 인자가 지나치게 많아지는 경우가 있지만,
장황한 정의는 장황한 구현에 비해 코드 이해와 유지보수 측면에서 별 문제
가 되지 않는다.
• 비독립 단위전략을 피해야 함
• 어쩔 수 없다면 캡슐화를 포기하고 의존성 최소하는 방향으로 분리
• 예) Array 단위전략과 Destroy 단위전략의 충돌
요약
• 단위전략 클래스 구현
• 적은 수의 기본적인 장치들을 조합해서 유연한 코드를 생성해야 함
• 사용자들도 자신만의 구현체를 만들 수 있어야 함
• 인터페이스 보강, 커스터마이징, 형변환 유연성
• 클래스 분리
• Trade-off 관계거나 다양한 방법으로 구현될 수 있다면 찾아내어 분리
• 다른 단위전략에 영향을 끼치지 않도록 독립적으로 구현