[문제점]
아래의 코드에서 List<int> s 가 멀티스래드에 안전하지 않다고 가정한다. 동기화를 위해서 Lock/Unlock의 구현이 필요하다. 그런데 사용자에 따라 List에 내장된 Lock/Unlock을 원치 않을 수도 있다. 불필요한 Lock/Unlock의 경우 성능 저하가 발생할 수도 있기 때문이다. 그러므로 Lock/Unlock은 사용자가 포함시킬 것인지 포함시키지 않을 것인지 선택 할 수 있어야 하겠다.
1. 변하는 것과 변하지 않는 것을 분리한다.
* 변하는 것을 가상함수화하는 방법 -> Template Method
* 변하는 것을 별도의 클래스로 구현하는 방법 -> Strategy Pattern
2. Strategy pattern을 이용해서 Lock/Unlock을 구현해보자.
#include <iostream>
using namespace std;
template<typename T> class List
{
public:
void setSync(ISync* p) { pSync = p; }
void push_front(const T& a)
{
//Lock();
// ...
//Unlock();
}
};
List<int> s;
int main()
{
s.push_front(10);
return 0;
}
2.1 다음과 같이 인터페이스 ISync를 정의하고 MySync 클래스가 ISync를 상속/구현 한다. List 클래스 내에서 ISync의 Lock() / Unlock()을 호출하도록 한다. MySync의 정책에 적용되었다.
#include <iostream>
using namespace std;
class ISync
{
public:
virtual void Lock() = 0;
virtual void Unlock() = 0;
};
template<typename T> class List
{
ISync* pSync = 0;
public:
void setSync(ISync* p) { pSync = p; }
void push_front(const T& a)
{
// Lock();
if( pSync != 0 ) pSync->Lock();
// ...
// Unlock();
if( pSync != 0 ) pSync->Unlock();
}
};
class MySync : public ISync
{
public:
virtual void Lock() { cout<<"MySync Lock" << endl;}
virtual void Unlock() { cout<<"MySync Unlock"<< endl;}
};
List<int > list;
int main()
{
MySync sync;
list.setSync(&sync);
list.push_front(10);
return 0;
}
2.2 그런데 이 구현에서 Lock/Unlock은 가상함수이다. 가상함수는 인라인 치환이 되지 않는다. 이러한 오버해드는 해결할 수 없는 것일까? 이를 위해 Lock/Unlock을 inline함수로 변경한다.
3. 아래의 코드와 같이 List 클래스의 템플릿 파라미터에 ThreadModel을 추가 하여 외부에서 정책을 결정할 수 있도록 해준다.
3.1 가상함수를 사용하는 ISync를 모두 삭제하고 Lock/Unlock의 구현체인 MutexLock()과 Nolock에서 Lock()/Unlock()을 inline함수로 만들어주고, 구현한다.
3.2 이는 최적화 단계에서 Lock() Unlock()이 인라인 치환을 가능하게 하며, 아무 구현체가 없는 경우에는 완전히 삭제되므로 성능 저하를 막을 수 있다. 다만 새로운 정책을 구현해야할 경우, 정책에 대해 참고할만한 부모 클래스가 없어 별도의 문서나 주석 또는 분석을 통해 추가구현에 대한 방법을 알아내야하는 단점이 있다.
#include <iostream>
using namespace std;
template<typename T, typename ThreadModel> class List
{
//ISync* pSync = 0;
ThreadModel tm; // 동기화 정책을 담은 클래스
public:
//void setSync(ISync* p) { pSync = p; }
void push_front(const T& a)
{
//Lock();
tm.Lock();
// ...
//Unlock();
tm.Unlock();
}
};
class MutexLock
{
public:
inline void Lock() { cout << "Mutext lock" << endl; }
inline void Unlock() {cout << "Mutex Unlock" << endl; }
};
class Nolock
{
public:
// 아래의 비어있는 인라임 함수가 치횐되면 최적화 단계에서 Lock/Unlock 호출 자체에 대한 기계어 코드가 사라진다.
inline void Lock() {}
inline void Unlock() {}
};
List<int, Nolock> s1;
List<int, MutexLock> s2;
int main()
{
cout<< "PolicyBasedDesign IN"<< endl;
s2.push_front(10);
cout << "PolicyBasedDesign OUT" << endl;
return 0;
}
4. 이렇게 정책 클래스를 Template 인자로 사용하는 방식을 "Policy Base Design"이라고 한다.
5. Policy Base Design과 Strategy Pattern과의 비교
5.1 전략 패턴은 가상함수 기반이고 느리다. 실시간 정책 교채가 가능하다.
5.2 단위 전략은 인라인 치환이 가능하여 빠르다. 컴파일 시간에 정책이 교채된다. 실행 시간에 교체 할 수 없다.
5.3 전략 패턴은 인터페이스를 참고해서 클래스를 구현할 수 있다.
5.4 단위 전략은 부모 클래스 자체가 없으니 정책을 어떻게 구현해야 하는지 모를 수 이 있다. -> 문서화 할 수 밖에 없다.
'프로그래밍 이야기 > C++ 기초' 카테고리의 다른 글
[C++] 일반 함수에서의 가변성 (0) | 2020.12.11 |
---|---|
[C++]Design Pattern - Application Framework (0) | 2020.12.07 |
[C++]Design Pattern - 변하는 것을 분리하기 (0) | 2020.12.01 |
[C++]Design Pattern - 변하는 것을 분리하기 (0) | 2020.12.01 |
[C++] 예제로 배우는 객체 지향(Prototype Pattern) (0) | 2020.11.25 |