[C++] 디자인 패턴 : Singleton(싱글톤) 완전정복👨🏻‍💻

1. Singleton이란?

Singleton은 디자인 패턴 중 하나로, 프로그램 전체에서 클래스의 인스턴스를 하나만 생성하도록 제한하고, 그 인스턴스에 전역적으로 접근할 수 있게 만드는 방식이다.

하지만 전역 인스턴스를 만드는 것이기 때문에, 잘못 사용하면 전역 변수와 같은 문제를 야기할 수 있으므로 신중하게 사용해야 한다.

 

2. 기본 구현 방법

아래는 가장 간단한 싱글톤 구현 예제이다.

class Singleton
{
public:
    static Singleton& getInstance()
    {
        static Singleton instance; // C++11 이후 thread-safe
        return instance;
    }

    void doSomething()
    {
        // 어떤 작업
    }

private:
    Singleton() {}  // 외부에서 생성 불가
    ~Singleton() {}
};

 

✅ 포인트 요약

  • 생성자를 private으로 선언하여 외부에서 인스턴스를 만들 수 없도록 한다.
  • getInstance()는 정적 함수로 하나의 인스턴스를 반환한다.

 

3. Thread-safe Singleton

C++11부터는 static local variable이 스레드 안전(thread-safe)하게 초기화되므로 위의 구현은 멀티스레드 환경에서도 안전하다.

  •  오래된 코드를 보면 mutex를 이용하여 수동 동기화를 했는데, C++11부터는 그럴 필요가 없다 !

 

4. 생성 방법 : Lazy vs Eager Initialization

Singleton은 생성 방법에 따라 두가지로 나뉜다.

 

4-1) 💤 Lazy Initialization (지연 초기화)

Singleton 인스턴스를 내가 실제로 필요할 때 생성하는 방식

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;  // 최초 호출 시에만 생성됨
        return instance;
    }

private:
    Singleton() {}
};

✅  장점

  • 사용하지 않으면 생성되지 않으므로 리소스를 절약할 수 있음.
  • 생성 순서를 명확하게 제어할 수 있음 (사용 시점 = 생성 시점).
  • C++11 이후에는 static 지역 변수의 초기화가 thread-safe하므로 동기화 걱정이 없음.

❌  단점

  • 초기화 타이밍이 예측되지 않음: 프로그램 흐름에 따라 인스턴스가 언제 생기는지 명확하지 않을 수 있음.
  • 프로그램 종료 시 파괴 순서 문제가 생길 수 있음 (특히 여러 static 객체와 얽혀 있을 때).

 

4-2) ⚡ Eager Initialization (즉시 초기화)

프로그램 시작 시점에 미리 생성해두는 방식

class Singleton {
public:
    static Singleton& getInstance() {
        return instance;
    }

private:
    Singleton() {}
    static Singleton instance;  // main 전에 이미 생성됨
};

Singleton Singleton::instance;

✅ 장점

  • 초기화 타이밍이 정해져 있어서 예측 가능.
  • 여러 객체가 동시에 의존할 경우 초기화 순서 문제를 줄일 수 있음.

❌ 단점

  • 실제로 쓰이지 않더라도 생성됨 → 불필요한 리소스 사용 가능성.
  • 생성 시점에 예외가 발생하면, 프로그램이 시작도 못 하고 실패할 수 있음.

 

4-3) ✍️  언제 어떤 걸 써야할까?

  • Lazy가 기본 선택: 대부분의 경우에 효율적이고 간단함.
  • Eager는 다음과 같은 상황에 유용:
    • 초기화 순서가 명확해야 하는 복잡한 시스템
    • 반드시 생성되어야만 하는 critical 자원
    • 프로그램 시작 단계에서 무조건 사용됨이 보장될 때

 

5. Singleton의 단점

이렇게만 보면 무적인 거 같지만, 당연히 단점도 존재한다는 사실 !

  • 테스트 어려움: 전역 인스턴스를 mocking하기 어렵다.
  • 의존성 숨김: 클래스 내부에서 몰래 singleton을 참조하면 추적이 어렵다.
  • 멀티스레드 문제: 잘못 구현되면 race condition 발생 가능.
  • 라이프사이클 제어 어려움: 생성 시점, 소멸 시점을 제어하기 힘들다.

이 때문에 무분별한 singleton 사용은 코드 품질을 악화시킬 수 있다.

 

6. 마무리 : 언제 Singleton을 쓸 것인가?

Singleton은 다음 조건을 만족할 때만 사용하는 것이 좋다:

  • 인스턴스가 하나만 존재해야 하는 강한 논리적 이유가 있을 때
  • 해당 인스턴스에 전역적으로 접근할 필요가 있을 때
  • 멀티스레드 환경에서 정상적으로 동작하도록 설계되었을 때

가능하다면 Singleton 대신 의존성 주입이나 명시적 객체 전달을 사용하는 것이 유지보수와 테스트 측면에서 훨씬 유리하다.