C++) 싱글톤(Singleton) 패턴
싱글톤(Singleton) 패턴은 “프로그램 전체에서 하나의 인스턴스만 존재해야 하는 클래스”를 구현할 때 사용하는 대표적인 디자인 패턴이다.
로그 관리자, 설정 관리자, 자원 풀(Resource Pool) 등 “전역적으로 통일된 접근점이 필요한 기능”에 자주 쓰인다.
싱글톤 구현 예시
정적 지역 변수 방식(Meyers Singleton)
C++11 이후 표준에서는 “정적 지역 변수를 초기화할 때” 쓰레드 안전성을 보장한다.
따라서 가장 간단하면서도 안전한 방법은 함수 스코프의 static 객체를 사용하는 것.
#include <iostream>
class Singleton
{
public:
// 인스턴스에 접근하는 정적 함수
static Singleton& getInstance()
{
// 첫 호출 시점에 한 번만 초기화되고, 쓰레드 안전.
static Singleton instance;
return instance;
}
// 예시 멤버 함수
void doSomething() {
std::cout << "Doing something with singleton!\n";
}
private:
// 생성자와 소멸자를 private으로 막아 외부에서 직접 생성/파괴하지 못하도록
Singleton() { std::cout << "Singleton Constructor\n"; }
~Singleton() = default;
// 복사 및 대입 연산자를 삭제해 복제 불가능하게 함
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
int main()
{
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
// s1, s2는 동일 인스턴스를 가리킴
s1.doSomething();
s2.doSomething();
return 0;
}
메모리 영역 관점
- static Singleton instance는 정적(Static) 스토리지 수명을 갖지만, “함수 스코프의 정적 지역 변수”라는 점에서 첫 실행 시점에 할당 및 생성된다.
- “static 지역 변수”는 데이터 영역(data segment)에 저장되거나, 컴파일러 구현에 따라 “정적 영역 어딘가”에 배치되지만, 실제 객체 생성(생성자 호출)은 “처음 getInstance()가 실행될 때” 이루어진다.
- C++11 이후에는 정적 지역 변수의 초기화가 쓰레드 안전이므로, 멀티스레드 환경에서도 안전하게 초기화가 보장된다.
소멸 시점
- 일반적으로 프로그램이 종료될 때(정확히는 정적 객체 소멸 단계) 소멸자가 호출.
- 우리는 수동으로 delete할 수 없다.( private 생성자/소멸자 + 정적 객체 ).
장점
- 구현이 간단, 쓰레드 안전, 사용하기 전까지 실제 생성이 미뤄짐(지연 초기화)
단점
- “해제 타이밍”을 내가 제어하지 못한다.
- 프로그램 종료 시에나 자동으로 소멸되므로, 수명 관리를 세밀하게 하기 어렵다.
- 정적 객체의 소멸 순서 문제가 발생할 수도 있다.
힙(Heap) 할당 방식
“정적 포인터 + new” 방식이다. 이 경우 힙(Heap)에 할당되며, 싱글톤 포인터를 정적 멤버 변수로 관리한다.
class Singleton
{
public:
static Singleton* getInstance()
{
// 더블 체크 락 등 멀티스레드 안전 장치 필요
if (!instance)
{
instance = new Singleton();
}
return instance;
}
void doSomething() { /* ... */ }
void release() {
if (instance) {
delete instance;
instance = nullptr;
}
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
static Singleton* instance; // 정적 포인터
};
// 정적 멤버 변수 정의
Singleton* Singleton::instance = nullptr;
소멸 시점
- 사용자가 원할 때 delete instance;로 수동 해제할 수 있다.
- 혹은 프로세스 종료 시 OS가 메모리를 가져가므로, “굳이 해제 안 해도” 시스템적으로 메모리 누수는 남지 않지만, C++ 객체의 소멸자 호출은 하지 않고 끝낼 수 있기에 주의가 필요.
장점
- 원하는 시점에 수동으로 소멸할 수 있다.(예: release() 호출)
- 어떤 경우엔 프로그램이 종료하기 전에 특정 자원을 깔끔히 정리해야 할 수도 있는데, 이 방식이면 가능.
단점
- 멀티스레드 환경에서 초기화와 파괴가 안전하려면 동기화가 필요.
- “정말로 하나만 존재”하는지 보장하기 위해서는 private 생성자를 지키고, 복사 대입 연산자도 막고, 접근을 엄격히 관리해야 한다.
댓글남기기