싱글톤 패턴(Singleton pattern)
싱글톤 패턴은 프로그램이 시작될 때 최초 한 번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴입니다.
즉, 싱글턴 패턴은 클래스의 인스턴스 생성을 단 한 번으로 제한하여 사용하는 방식입니다.
싱글턴 패턴은 프로그램 내에서 공통적으로 쓰이는 자원을 관리, 저장하는 역할을 할 때 사용하거나, Connection pool, Thread Pool과 같은 공통된 객체를 여러 개 생성해서 사용해야 하는 경우에 많이 사용됩니다.
(메모리, 리소스 등을 관리해야 하는 class를 싱클톤 패턴으로 만들어서 사용)
싱글톤 패턴은 class에 자기 자신을 static 멤버로 잡고, getInstance()라는 static 함수를 사용하여 자기 자신의 객체를 반환하여 사용합니다.
이때 자기 자신을 일반 변수로 할지 포인터로 할 지에 따라 2가지 방식이 있습니다.(이 2가지 방식 이외에도 더 많은 방식이 있습니다.)
기본 싱글톤(Basic Singleton)
class C_DATA
{
private:
int m_nData;
static C_DATA m_cData;
private:
C_DATA();
public:
static C_DATA* getInstance();
void setData(int nData);
int getData();
};
클래스 멤버로 static 멤버로 자기 자신을 가지고 있고, 이를 반환하는 static 함수 getInstance() 함수가 있습니다.
C_DATA C_DATA::m_cData;
C_DATA::C_DATA() :
m_nData(0)
{
}
C_DATA * C_DATA::getInstance()
{
return &m_cData;
}
void C_DATA::setData(int nData)
{
m_nData = nData;
}
int C_DATA::getData()
{
return m_nData;
}
static 정적 변수의 특성상 프로그램 시작하자마자 메모리를 잡고 초기화를 해야 하기 때문에 1행에서 "C_DATA C_DATA::m_cData;"를 통해서 static 정적 변수를 초기화해주고 있습니다.
static 정적 함수 getInstance는 이 static 정적 클래스 변수 m_cData의 주소값을 반환하고 있습니다. 이 함수를 통해 인스턴스에 접근하여 멤버 함수에 접근하여 사용합니다.
int main()
{
C_DATA::getInstance()->setData(100);
printf("%d \n", C_DATA::getInstance()->getData());
return 0;
}
namespace를 사용하여 static 정적 함수 getInstance()를 호출하고 이 함수는 인스턴스를 반환하므로 이를 통해 멤버 함수 호출을 하고 있습니다.
<단점>
static 클래스 멤버 변수는 프로그램 시작할 때 메모리를 잡고 초기화가 되기 때문에 메모리 낭비가 발생합니다.
즉, 언제 사용할지도 모르고, 사용하지 않을 수도 있는데 프로그램 시작부터 메모리를 잡고 있기 때문에 메모리를 비효율적으로 사용하게 됩니다.
따라서 이러한 방식은 사용하지 않습니다.
다이나믹 싱글톤(Dynamic Singleton)
class C_DATA
{
private:
int m_nData;
static C_DATA* m_pData;
private:
C_DATA();
public:
static bool createInstance();
static void releaseInstance();
static C_DATA* getInstance();
void setData(int nData);
int getData();
};
늦은 초기화 방식을 사용하여 싱글톤입니다.
멤버로 자기 자신을 static 포인터 변수 구성합니다.
C_DATA* C_DATA::m_pData = nullptr;
C_DATA::C_DATA() :
m_nData(0)
{
}
bool C_DATA::createInstance()
{
if (m_pData)
return false;
m_pData = new C_DATA();
return true;
}
void C_DATA::releaseInstance()
{
if (m_pData) {
delete m_pData;
m_pData = nullptr;
}
}
C_DATA * C_DATA::getInstance()
{
return m_pData;
}
void C_DATA::setData(int nData)
{
m_nData = nData;
}
int C_DATA::getData()
{
return m_nData;
}
자기 자신을 포인터 변수로 만들었으므로 1행에서 초기화도 "C_DATA* C_DATA::m_pData = nullptr;" 이렇게 nullptr로 초기화해 줍니다.
이제 이 static 정적 포인터 변수를 동적할당 하여 사용해야 하기 때문에 createInstance() 함수에서 동적할당을 합니다. 동적할당을 했다면 반드시 메모리를 해제하는 작업이 필요하므로 releaseInstance() 함수를 통해 메모리 해제를 합니다.
int main()
{
C_DATA::createInstance();
C_DATA::getInstance()->setData(100);
printf("%d \n", C_DATA::getInstance()->getData());
C_DATA::releaseInstance();
return 0;
}
기본 싱글톤과는 다르게 getInstance()를 사용하기 위해서는 createInstance() 함수를 통해 동적할당을 하고 사용을 다 했다면 마지막에 releaseInstance() 함수를 통해 메모리를 해제하는 작업을 해주어야 합니다.
기본 싱글톤과 다르게 static 클래스 포인터 멤버 변수를 사용하기 때문에 프로그램 시작 시 메모리를 4byte만 잡고 시작하게 되어 기본 싱글톤 보다 메모리를 효율적으로 사용할 수 있으며, 또 Instance를 사용하기 위해서는 createInstance() 함수와 같은 동적할당을 담당하는 static 함수를 호출한 뒤 사용하기 때문에 사용 시 기점이 명확합니다.
물론 동적할당을 했기 때문에 사용이 끝났다면 releaseInstance() 함수와 같이 메모리를 해제하는 기능이 필요합니다.
싱글톤 단점
1. 메모리 낭비
어찌 되었든 static 정적 변수는 프로그램 시작 시 메모리를 잡고 초기화를 진행하기 때문에 싱글톤 헤더를 추가하고 만약 사용하지 않게 된다면 메모리 낭비가 발생하게 됩니다.
2. 멀티 스레드 프로그램에서의 문제
싱글 스레드 프로그램에서는 문제가 되지 않지만 멀티 스레드 프로그램인 경우 static 정적 변수가 초기화되기 전에 static 정적 함수를 사용하게 되면 문제가 발생하게 됩니다.
3. 객체 지향의 원칙 파괴
싱글톤은 헤더만 추가하면 어디서든 사용할 수 있기 때문에 다른 클래스들 간에 결합도가 높아지게 됩니다.
따라서 싱글톤을 남발하여 사용하게 되면 프로그램 구조가 꼬이게 됩니다.
'디자인 패턴' 카테고리의 다른 글
[디자인 패턴]데코레이터 패턴(Decorator pattern) (0) | 2018.03.11 |
---|