C++에서 Singleton의 구현은 몇가지 방법이 있다.
이중 내가 주로 사용하는 방법은 아래의 방법이다.
class A
{
private:
static A* m_Instance;
public:
static A* Instance()
{
if( m_instance == NULL )
{
m_Instance = new A();
}
return m_Instance;
}
};
위와 같이 구현된 형태가 내가 사용하는 Singleton의 기본 형태이다.
여기에 몇가지 추가 기능이 들어간다.
첫번째는 thread-safe를 위해서 Lock을 사용하는 것이다.
m_Instance가 여러 쓰레드에서 접근하기 때문에 두개 이상 생기는 것을 방지하기 위함이다.
그렇지만 매번 NULL 체크를 할 때마다 Lock을 잡는 것은 성능상 큰 문제가 있다.
그래서 사용하는 것이 double checked lock 패턴이다.
구현은 아래와 같다.
inline A* A::Instance()
{
if( m_Instance == NULL ) // #1
{
m_Lock.Aquire();
if( m_Instance == NULL ) // #2
{
m_Instance = new A();
}
m_Lock.Release();
}
return m_Instance;
}
이렇게 사용하려면 m_Instance는 아래와 같이 volatile로 선언이 바뀌어야 한다.
A * volatile A::m_Instance;
이것이 안들어가게 되면 Complier optimization에 의해서 #1에서 읽은 값과 #2에서 읽은 값이 항상 같게 되어서 Lock을 잡는 의미가 없어질 가능성이 있다.
당연히도 volatile을 쓰면 안정적인 동작이 되지만 성능은 떨어지게 된다.
Singleton은 Instance() 콜이 매우 잦을 가능성이 많기 때문에 성능이 저하되는 것은 안될 일이다.
이것을 해결하기 위해서 추가적인 함수를 사용해서 volatile을 제거한다.
class A
{
...
static A* CreateInstance();
...
};
inline A* A::Instance()
{
if( m_Instance != NULL )
{
return m_Instance; // #3
}
return CreateInstance();
}
//-------------
// in .cpp
A* A::CreateInstance()
{
m_Lock.Aquire();
if( m_Instance == NULL )
{
m_Instance = new A(); // #4
}
m_Lock.Release();
return m_Instance;
}
이렇게 inline이 아닌 함수로 생성을 위임하는 것을 통해서 volatile을 제거할 수 있다.
inline이 아니기 때문에 CreatInstance() 안에서 m_Instance를 새로 읽어올 것을 보장할 수 있다.
(물론 특정한 컴파일 옵션에 의해서 강제로 인라인 화 될 경우 어떻게 될지는 모르나 그렇게 까지 쓸경우는 없을 것이다.)
위의 예에서 Instance() 함수는 처음 생성될 때를 제외하고는 if 조건 하나를 조사하고 #3이 불리는 형태로 진행이 된다.
이것은 인라인화가 될 것이고 CPU의 branch prediction까지 대부분을 적중 시킬 것이다.
그렇기 때문에 실제 A::Instance()를 콜하면 수행되는 코드는 m_Instance를 직접 참조하는 것과 거의 차이가 나지 않을 것이다.
마지막으로 추가되는 기능은 프로그램 종료시 메모리를 정리하는 부분이다.
즉 new A() 로 생성된 m_Instance를 지워야 한다.
이것은 stdlib에 존재하는 atexit() 함수를 이용해서 구현하면 된다.
#4의 부분에 atexit()로 자신의 instance를 삭제하는 함수를 등록한다.
class A
{
...
private:
static void __cdecl DestoryInstance();
...
};
void __cdecl A::DestoryInstance()
{
delete m_Instance;
}
#4 다음 줄에 아래 추가
atexit(&DestoryInstance);
이렇게 되면 프로그램 종료직전에 DestoryInstance() 가 불려서 할당된 객체를 삭제하게 된다.
출처 : http://kimsk99.blog.me/50007325992
참고 : http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
'Programming > C / C++' 카테고리의 다른 글
Windows Named Pipe 구현 간단 정리 (0) | 2015.06.11 |
---|---|
GetSystemMetrics (0) | 2015.06.04 |
URLDownloadToFile example (0) | 2015.05.26 |
GetCurrentDirectory() vs GetModuleFileName() (0) | 2015.05.22 |
빠른 음수화 (0) | 2015.02.07 |