policy based class design

24
Modern C++ Design 단위전략 기반의 클래스 디자인

Upload: lactrious

Post on 26-May-2015

291 views

Category:

Engineering


2 download

DESCRIPTION

Modern C++ Design, Chapter 01: Policy based Class Design

TRANSCRIPT

ModernC++Design

단위전략 기반의

클래스 디자인

Modern C++ Design

• Generic programming

• Design Pattern Applied

단위전략 기반의 클래스 디자인

무엇인가?

왜 써야하나?

어떻게 구현하나?

무슨 장점이 있나?

무슨 장점이 있나?

단위전략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 관계거나 다양한 방법으로 구현될 수 있다면 찾아내어 분리

• 다른 단위전략에 영향을 끼치지 않도록 독립적으로 구현

끝참 쉽죠?