본문 바로가기
C++

[C++ Core Guidelines] I.3 싱글톤은 가급적 쓰지 말자

by 코드쉼터 2024. 5. 4.

I.3: Avoid singletons

 

이유

싱글톤은 기본적으로 복잡한 전역 변수일 뿐입니다. (I.2 의 경우처럼 함수에 숨겨진 의존성을 주입합니다)

 

 

예시 (나쁜 예)

class Singleton {
    // ... 싱글톤 객체가 생성되려면 참 많은 것들이 필요합니다 ...
    // 제대로 초기화 되었는지, 이미 된 상태인지 등
};

싱글톤을 구현하는 방법에는 static 멤버를 활용하는 방법, nullptr 을 사용하는 방법 등 여러가지가 있습니다.

바로 이것이 문제를 일으킵니다.

 

 

알아두기

만약 전역 개체가 변경되는 것을 막는 것이 목표였다면, 싱글톤 대신 전역 변수를 const 또는 constexpr로 선언하세요.

 

 

예시 (괜찮을 수도 있는 예)

X& myX()
{
    static X my_x {3};
    return my_x;
}

아주 단순한 "싱글톤" 을 사용하여 클래스가 처음 사용될 때만 초기화가 수행되도록 유도하는 것은 괜찮습니다.

사실 싱글톤은 초기화 순서와 관련된 문제에 대한 가장 효과적인 솔루션 중 하나입니다. 초기화 이후에는 읽기만 발생하므로 멀티 스레드 환경에서 정적 개체의 초기화로 인해 데이터 경쟁 조건이 발생하지 않습니다. (물론 생성자에서 공유 개체에 부주의하게 액세스하면 안됩니다)

그러나 X 를 파괴하는데 동기화가 필요한 작업이 포함되는 경우 아래의 복잡한 방법을 사용해야 합니다.

 

 

예시 (나쁜 예)

X& myX()
{
    static auto p = new X {3};
    return *p;  // 누수 발생가능
}

누군가가 적절하게 멀티 스레드로 환경에서 안전하게 위 개체를 삭제해야 할 때가 왔다고 합시다.

이제 오류를 피하기 위해 아래와 같은 것들을 신경써야 할 순간이 왔습니다.

1. myX 가 멀티 스레드로 돌고있는 코드 어딘가에서 사용중일지 모릅니다.
2. 해당 X 개체는 언젠간 파괴되어야 합니다 (예: 사용이 끝난 자원이라서 반환해야 하는 경우)
3. X 의 소멸자 코드를 동기화해야 합니다.

싱글톤은 인스턴스가 반드시 하나임을 보장해야 하는데 위와 같은 문제들을 신경쓰다 보면 더이상 싱글톤이라고 부를 수 없는 클래스가 만들어지게 됩니다.

싱글톤을 멀티스레드로 동기화 시키기 위해 DCLP 라는 기법을 사용합니다만, C++ 컴파일러 최적화 (re-ordering) 때문에 충돌하는 문제가 있으므로 반드시 C++11 의 std::atomic 을 사용해야만 합니다

게다가 다른 파일 (translation units) 에서 싱글톤 여러개가 뒤섞여 의존하고 있던 경우, 초기화/소멸 순서 문제 (static initialization order problem) 도 발생할 수 있습니다.

 

 

정리

1. 싱글톤은 쉬워 보이지만 적절히 쓰기는 매우 어렵습니다. (남용되고 있으니 주의하세요)
2. 이름에 싱글톤이 포함된 클래스를 Ctrl+F 로 찾으세요.
3. (개체 수를 세거나 생성자를 검사하는 방법을 통해) 단일 개체만 생성하는 클래스도 찾으세요.
4. 만약 그 클래스 내부에 public static 함수가 있어서 스스로 생성한 static 개체에 대한 포인터나 참조를 반환하려는 경우 해당 클래스를 날려버리세요.

 

 

참고자료

https://wendys.tistory.com/14