싱글턴 패턴이란 단 하나만의 객체만을 만들고, 어디서든지 접근 가능할 수 있도록 하는 디자인 패턴이다. 예를 들어 scene들을 관리하는 SceneManger라는 클래스를 싱글톤으로 사용해서 하나의 객체만을 생성해서 관리하도록 했다. 이런 Manager 객체가 여러 개 만들어진다면, 다른 Manager 객체가 scene같은 resource에 어떤 변화를 주는지 파악하기 힘들기 때문에 싱글턴 패턴을 이용했다.
기본적으로 싱글톤 패턴은 다음과 같이 매크로로 설정해놓고 싱글턴을 사용해야 하는 클래스에서 매크로를 이용해 심플하게 사용할 수 있다.
// Macro.h
#define DECLARE_SINGLE(Type) \
private:\
static Type* m_pInst;\
public:\
static Type* GetInst()\
{\
if (!m_pInst)\
m_pInst = new Type; \
return m_pInst; \
}\
static void DestroyInst()\
{\
if(p)\
{\
delete p;\
p = NULL;\
}\
}\
private:\
Type(); \
~Type();
// 싱글턴 인스턴스를 가리키는 m_pInst는 static 멤버 변수니까 항상 이렇게 클래스 밖에서 선언해줘야함
#define DEFINITION_SINGLE(Type) Type* Type::m_pInst = NULL;
// CSceneManager.h
class CSceneManager
{
/*
원하는 멤버 변수나 멤버 함수 작성
*/
DECLARE_SINGLE(CSceneManager)
};
// CSceneManager.cpp
DEFINITION_SINGLE(CSceneManager)
사실 이 코드는 완벽히 기본적인 싱글턴이 아니라 static 멤버 변수가 프로그램 시작시 main 호출 전 할당된다는 단점을 보완하고자 늦은 초기화 개념을 적용한 다이나믹 싱글턴(Dynamic Singleton)이다. 별건 아니고 프로그램 시작과 동시에 static 멤버 변수를 위한 공간이 할당되는게 아니라, GetInst() 함수를 처음 호출할 때가 돼서야 싱글턴 객체를 할당하는 것이다. 예를 들어 다이나믹 싱글턴을 사용하지 않는다면 아래와 같은 코드가 될 것이다.
#define DECLARE_SINGLE(Type) \
private:\
static Type m_Inst;\
public:\
static Type& GetInst()\
{\
return m_Inst; \
}\
private:\
Type(); \
~Type();
따라서 위와 같이 그냥 가장 기본적인 싱글턴 패턴으로 만든다면 static 멤버 변수는 전역 변수처럼 프로세스가 시작할 때(main 함수 전)부터 싱글턴 객체가 아직 필요없는데도 메모리가 할당되고 시작하는 상황이 생길수도 있다. 특히 싱글턴 객체를 하나도 만들지 않아도 될 상황에서는 더욱 낭비가 되기 때문에 싱글턴 패턴을 사용해야 한다면 다이나믹 싱글턴이 더 나은 선택으로 보인다.
그리고 당연한 이야기이지만 프로그램이 종료될 때는 당연히 싱글턴 객체도 해제해줘야 한다. 나의 경우 Manager클래스들을 관리하는 Core라는 또 다른 클래스를 만들어서 Core클래스의 소멸자에서 싱글턴 객체들의 메모리를 해제해주었다.
장점
- 객체가 하나만 만들어지는 것을 보장해서 메모리 낭비를 방지할 수 있음
- 어떤 클래스에서도 접근 가능하므로 여러 클래스 인스턴스들이 데이터를 공유하기 쉽다
단점
- 생성자가 private으로 제한되어 있어서 객체 지향에서 자주 쓰이는 상속을 활용할 수 없다
- 멀티 스레드 환경에서 동기화처리가 올바르게 되지 않는다면 인스턴스가 2개가 생성되는 문제가 발생할 수도 있다
Reference
https://boycoding.tistory.com/109
https://vallista.tistory.com/entry/1-Singleton-Pattern-in-C