무분별한 객체생성을 방지하고, 1개의 객체만 생성하여 이용하는 프로그램 코딩에 유용하게 적용할 수 있다.
일반적인 싱글톤 패턴의 코드는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Singleton { private : Singleton() {} ~Singleton() {} Singleton( const Singleton& s) {} public : static Singleton* GetInstance() { if (m_pInstance == NULL) { m_pInstance = new Singleton; } return m_pInstance; } private : static Singleton* m_pInstance; }; //------------------------------------------------------------------ // Main int _tmain( int argc, _TCHAR* argv[]) { Singleton* pSingleton = Singleton::GetInstance(); return 0; } |
문제가 없는듯 보이는데 그러나 이 패턴에는 문제점이 존재한다. 바로 new로 생성한 객체의 소멸을 보장받지 못하는 것이다. (리소스 누수현상 발생)
이러한 누수현상에 대처하기 위해서 동적 생성이 아닌 정적 생성 사용을 예로 들 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Singleton { private : Singleton() {} ~Singleton() {} Singleton( const Singleton& s) {} public : static Singleton* GetInstance() { return &m_pInstance; } private : static Singleton m_pInstance; }; //------------------------------------------------------------------ // Main int _tmain( int argc, _TCHAR* argv[]) { Singleton* pSingleton = Singleton::GetInstance(); return 0; } |
정적 객체를 생성하여 프로그램 종료시 객체 반환할 필요가 없어졌다.
처음으로 돌아가서 동적 생성을 통한 싱글턴 패턴 인스턴스 소멸은 어떻게 가능할 수 있는지 확인해보도록 하자.
atexit() 함수에 대하여
atexit 함수는 프로그램 종료시에 (main함수 종료 직전) 실행되며, 인자는 함수 포인터를 가진다.
int atexit(
void (__cdecl *func )( void )
);
이 정의에서 알수 있듯이 atexit 함수는 인수를 취할 수 없고, 리턴값도 없는 void func(void)형이어야 한다. 또한 이 함수는 32개까지 등록될 수 있으며 최후로 등록된 함수가 가장 먼저 실행된다.
atexit함수를 이용하면 프로그램 종료 직전에 내가 원하는 작업을 할 수 있는 기능을 만들어 줄 수가 있게 된다.
아래 코드를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include "stdafx.h" class Singleton { private : Singleton() {} ~Singleton() {} Singleton( const Singleton& s) {} private : static void destroy() { delete m_pInstance; } // 객체 소멸 public : static Singleton* GetInstance() { if (m_pInstance == NULL) { m_pInstance = new Singleton(); atexit (destroy); // 프로그램 종료 직전호출 } return m_pInstance; } private : static Singleton* m_pInstance; }; Singleton* Singleton::m_pInstance = NULL; //------------------------------------------------------------------ // Main int _tmain( int argc, _TCHAR* argv[]) { Singleton* pSingleton = Singleton::GetInstance(); return 0; } |
자 위 코드는 동적 객체를 생성하고 atexit()함수를 이용하여 main()함수 종료 전에 객체를 delete하도록 하였다. 이렇게 하면 객체 생성 및 해제를 아주 깔끔하게 해결 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include "stdafx.h" //------------------------------------------------------------------ // template singleton pattern template < typename T> class Singleton { protected : Singleton() {} virtual ~Singleton() {} Singleton( const Singleton& s) {} private : static void destroy() { delete m_pInstance; } // 객체 소멸 public : static T* GetInstance() { if (m_pInstance == NULL) { m_pInstance = new T(); atexit (destroy); // 프로그램 종료 직전호출 } return m_pInstance; } private : static T* m_pInstance; }; template < typename T> T* Singleton <t>::m_pInstance; //------------------------------------------------------------------ // test code class 테스트 : public Singleton<테스트> { public : void 싱글턴_테스트() { cout << "템플릿 싱글턴 테스트 합니다." << endl; } }; //------------------------------------------------------------------ // Main int _tmain( int argc, _TCHAR* argv[]) { 테스트::GetInstance()->싱글턴_테스트(); return 0; } |
멀티 스레드 기반에서 싱글턴 패턴 구현 (volatile 활용)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | template < typename T> class CSingleton : public CMultiThreadSync<T> { protected : CSingleton() {} virtual ~CSingleton() {} CSingleton( const CSingleton& s) {} private : static void destroy() { delete m_pInstance; } // 객체 소멸 public : static T* GetInstance() { if (m_pInstance == NULL) { CThreadSync cs; m_pInstance = new T(); atexit (destroy); // 프로그램 종료 직전호출 } return m_pInstance; } private : static T* volatile m_pInstance; }; template < typename T> T* volatile CSingleton <T>::m_pInstance; |
스마트 포인터 shared_prt<> 을 활용한 싱글턴 패턴
스마트 포인터와 shared_ptr 이란?
- 스마트 포인터 : heap 메모리에 new, malloc으로 할당된 경우 자동으로 free, delete(소멸) 해주는 똑똑한 포인터 (auto_ptr, shared_ptr, weak_ptr 등이 있다)
- shared_ptr : 참조 횟수(reference counting)에 따른 메모리 할당 해제를 수행한다. 즉, 최초 객체를 할당시 실질적으로 메모리에서 할당받고, 그 다음부터는 객체 복사시 실제 복사는 이루어지지 않고 카운팅을 올려서 관리하는 기법이다. 또한 카운팅이 0가 되었을 경우 메모리를 자동 해제하게 된다.
shared_ptr<자료형> 이름(사이즈);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | template < typename T> class CSingleton { public : static T* GetInstance() { std::call_once(m_onceFlag, [] { m_pInstance.reset( new T); }); return m_pInstance.get(); } private : static std::shared_ptr<T> m_pInstance; static std::once_flag m_onceFlag; }; template < typename T> std::shared_ptr<T> CSingleton<T>::m_pInstance = NULL; template < typename T> std::once_flag CSingleton<T>::m_onceFlag; |
우리는 위에서 atexit() 함수를 활용하여 소멸자 역할을 할 수 있는 함수를 프로그램 종료직전에 명시적으로 호출하도록 하였다. 하지만 이 기능은 뭔가 꺼림찍하고 깔끔해보이지 않는다. 그래서 shared_ptr 스마트포인터를 활용하여 새로운 싱글턴 패턴을 구현하였다.
'CPP' 카테고리의 다른 글
STL 정렬 (0) | 2015.12.23 |
---|---|
STL 기본 (0) | 2015.12.23 |
모듈 이 safeseh 이미지 에 대해 안전 하지 않습니다 (0) | 2014.10.01 |
싱글톤 2 (0) | 2014.09.30 |