3. 단위전략 기반의 클래스 디자인
무엇인가?
왜 써야하나?
어떻게 구현하나?
무슨 장점이 있나?
무슨 장점이 있나?
4. 단위전략policy 기반의 클래스 디자인
• 매우 단순한 동작이나 구조만을 갖는 작은 클래스(policy)들을 모아
서 복합된 기능을 하는 새로운 클래스를 만들어내는 디자인 방식
• 단위전략들은 서로 간에 연동되거나 조합되어 사용될 수 있으므로
다양한 결과물을 얻어낼 수 있음
policy classes host class
5. 소프트웨어 디자인의 다양성
• 디자인 문제에 있어서 올바른 해법은?
• 단일 vs 다중 스레드, 안정성 vs 성능, 감시자 vs 콜백, 가상 함수 vs 템플릿
• 소프트웨어 시스템 디자인은 문제를 해결하는 다양한 해법들 중 올
바른 방법을 끊임없이 선택하는 과정
• 유연하고, 안전하고, 사용자가 쉽게 조절할 수 있고
적절한 양의 코드로 다양성의 재앙과 맞서 싸우기 위한 방법은?
6. Do-It-All 인터페이스의 문제점
• 모든 기능에 대한 인터페이스를 구현(Do-It-All 인터페이스)할 경우,
• 기능이 런타임에 수행되므로 컴파일 시점에 검사할 수 있는 내용이 축소됨
문법적으로는 옳지만 의미상으로는 옳지 않은 코드가 만들어질 수 있음
• 서로 다른 디자인 요소들을 작은 별개의 클래스로 구현할 경우,
• 지나치게 다양한 선택의 조합이 난립할 수 있음
• 미리 규정된 라이브러리 제한 조건보다는
사용자가 만든 디자인 스스로의 제한 조건을 가지도록 설계해야 함
• 프로그래머가 선택하고 커스터마이징을 할 수 있도록 설계해야 함
7. 다중 상속이 해결책이 될 수 있을까요?
• 잘 디자인된 기반 클래스 여러 개를 상속받는 것으로 될까?
• 기반 클래스의 조합은 가능하지만 부족한 부분이 있음
• 다중 상속은 단지 기반 클래스들을 포개어놓고 멤버를 접근하기 위한 기능
이므로 상속된 컴포넌트들을 제어하거나 취합하는 것은 직접 해야 함
• 기반 클래스들은 자신이 모르는 클래스에 대한 작업을 해야 함
• 기반 클래스가 상태를 가질 경우 가상 상속을 이용해야만 함
8. 템플릿의 이점
• 선택한 자료형에 따라 컴파일 시점에 코드를 컴파일러가 생성해줌
• 부분 특화Specialization를 통해 사용자가 특화된 기능 추가가 가능
• 다중 인자 클래스 템플릿의 부분 인자 특화로 인한 확장성
• 단점
• 멤버 변수 수준의 template 적용은 불가능
• 멤버 함수의 부분 특화는 불가능
• 다중 상속과 템플릿의 병용하여 라이브러리를 작성함
9. 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!
}
템플릿 인자를 부분 특수화할 수 있다.
완전 특수화를 하여 멤버 함수를 정의할 수는 있지만,
멤버 함수는 부분 특수화를 할 수 없다.
10. 단위전략과 단위전략 클래스
• 단위전략 클래스: 단위전략에 대한 구현물
• 명시적 인터페이스 구현이 아니라 template에 의해 모호하게 정의됨
• 호스트 클래스: 단위전략을 사용하는 클래스
• 특정 부분을 마음대로 구성할 수 있는 효과적인 방법을 제공
11. 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를 상속받아 구현한다.
12. 템플릿 템플릿 인자를 통한 구현
• 템플릿 템플릿 인자를 사용한 단위전략 구성
• 보다 유연한 코드를 작성할 수 있게 해줌
• 사용자가 보다 애플리케이션에 정확히 들어맞는 고유의 생성 전략
을 제공할 수 있음
13. 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을 상속받아야 한다.
14. 템플릿 멤버 함수를 통한 구현
• 구형 컴파일러에서도 잘 동작함
• 템플릿 클래스에 비해 논의, 정의, 구현, 사용법이 까다로움
struct OpNewCreator {
template <class T>
static T* Create() {
return new T;
}
};
15. 단위전략 인터페이스의 보강
• 라이브러리가 아닌 사용자 자신이 어떤 단위전략 클래스를 사용
• 기능을 지원하지 않는 다른 단위전략 클래스를 사용하면 컴파일러
가 경고
typedef WidgetManager<PrototypeCreator> MyWidgetManager;
// ...
Widget* prototype = CreatePrototype();
MyWidgetManager mgr;
mgr.SetPrototype(prototype);
// ... PrototypeCreator 단위전략 함수를 갖게되어 인터페이스가 보강됨
16. 단위전략 클래스의 소멸자
• 단위전략 클래스로 강제 casting 후 소멸자 호출 막기 위해 소멸자
를 protected로 변경
• 가상 소멸자를 정의하는 것은 불필요한 부담을 야기
typedef WidgetManager<PrototypeCreator>
MyWidgetManager;
MyWidgetManager mgr;
PrototypeCreator<Widget>* creator = &mgr;
delete creator; // ???
template <class T>
struct PrototypeCreator {
// ...
protected:
~PrototypeCreator() {}
};
17. 불완전한 구체화를 통한 부가기능
• 템플릿에서 전혀 사용되지 않는 멤버 함수는 구체화 시 무시됨
• 컴파일러에 따라 다르지만 심볼 유효성 등 문맥적 검사는 하지 않음
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()을 부르지 않으면 OK
PrototypeCreator를 쓸 경우 SwitchPrototype()을 불러도 OK
18. 단위전략 클래스 간의 조합
• 호스트 클래스는 여러 단위 전략을 조합하여 다양한 동작을 정의할
수 있음
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;
19. 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_;
};
적절한 단위전략을 선택하여 어떠한 동작을 할지 결정할 수 있다.
20. 단위전략 클래스를 통한 커스터마이징
• 단위전략과 상속을 사용하여 클래스의 구조를 변경
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에 필요한 저장 구조를 포함하기 위해 상속
21. 호환/비호환 단위전략
• 호환 가능한 단위전략을 위해 단위전략 간의 형변환 구현(복사 생성자
나 형변환 연산자) 후 단위전략 단위로 복사를 하도록 구현
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간의 복사 생성자가 구현되어 있을 때만 허용된다.
22. 클래스를 단위전략으로 분리해 내기
• 극단적인 경우 호스트 클래스는 단위전략의 집합체로만 표현됨
• 이 경우 템플릿 인자가 지나치게 많아지는 경우가 있지만,
장황한 정의는 장황한 구현에 비해 코드 이해와 유지보수 측면에서 별 문제
가 되지 않는다.
• 비독립 단위전략을 피해야 함
• 어쩔 수 없다면 캡슐화를 포기하고 의존성 최소하는 방향으로 분리
• 예) Array 단위전략과 Destroy 단위전략의 충돌
23. 요약
• 단위전략 클래스 구현
• 적은 수의 기본적인 장치들을 조합해서 유연한 코드를 생성해야 함
• 사용자들도 자신만의 구현체를 만들 수 있어야 함
• 인터페이스 보강, 커스터마이징, 형변환 유연성
• 클래스 분리
• Trade-off 관계거나 다양한 방법으로 구현될 수 있다면 찾아내어 분리
• 다른 단위전략에 영향을 끼치지 않도록 독립적으로 구현