Basic/Design Pattern

#1 Singleton Pattern in C++ (싱글톤 패턴 C++)

VallistA2014.08.22 15:58

Singleton Pattern


정말 기본적으로 사용하는 디자인 패턴인 Singleton

디자인 패턴 32가지 중에서 가장 많이 사용하면서도 편리하고, 동시에 메모리등 여러가지 문제점을 안고있기도 합니다.


이 싱글톤을 어느때 적절히 사용할 수 있냐 하면, 하나의 프로그램 내에서 공통적으로 쓰이는 자원을 관리, 저장하는 역할을 할 때 사용하거나

Connection Pool, Thread Pool과 같은 관리를 하는 (State Pattern) 클래스의 메인의 경우 Singleton을 사용하여 구현합니다.


근데, 이 singleton 종류가 굉장히 많다는 사실에 대해서 알고 계신가요?


이번 포스팅은 우리가 정말 기본적으로 사용한 디자인 패턴인 Singleton에 대해서 서술합니다.



기본 싱글톤 (Basic Singleton)

 정말 기본적인, 누구나 다 쉽게 구현할 수 있는 싱글톤 입니다.

 장점과 단점이 있으나 먼저 소스부터 살펴보도록 하겠습니다.


 정말 짧고 쉽습니다. 이걸로써 우리는 자원을 지속적으로 관리할 수 있는 클래스를 얻은 것입니다.

 생성자를 private으로하고... GetInstance라는 Singleton 즉 자기자신을 받는 함수를 하나 만들어서 미리 만들어 두었던 자기 자신 객체를 반환하는 클래스입니다.

 하지만 이렇게 짧고 쉬우면 단점도 존재하기 마련입니다.


 1) static 클래스 멤버 변수는 static 전역 변수처럼 프로그램 시작 시 main함수 호출 이전에 초기화가 되므로 프로그램이 어떻게 되든 메모리를 잡게되므로 어느 상황에서는 상당히 비효율적인 작업이 되어버립니다.

 2) 정적 개체이기 때문에, 다른 전역 객체의 생성자에서 참조하고 싶은 경우 문제가 발생하게 됩니다. 왜냐하면 C++ 표준에서는 전역 객체들의 생성 순서에 명확히 정의되어있지 않기 때문입니다.

 

즉 어떠한 전역 객체의 생성자에서 위 싱글톤을 참조하려고 할 때, 싱글톤 객체가 생성되기 이전에 발생할 수 있기 때문에 객체의 생성 시점을 변경을 해야 합니다. (맨처음이나.)


Effective C++에 보면 늦은 초기화에 대해서 알고 계시는 분들이 계실까요?

아마도 있으실것 입니다.


생성 시점을 변경하기 위해서 늦은 초기화를 사용을 해야 합니다. 한번 늦은 초기화를 사용하여 Singleton을 구현해보도록 합시다.


다이나믹 싱글톤 (Dynamic Singleton)

 위에서 대두된 문제점으로 인해 피해가도록 프로그래밍을 해야합니다.

 그래서 우리는 Effective C++에서 나온 늦은 초기화를 사용한 싱글톤을 만들어 보겠습니다.

 물론 이것도 굉장히 쉽습니다.


 다이나믹 싱글톤 (Dynamic Singleton)은 최초 GetInstance()를 호출을 하게 되는 시점에 생성이 됩니다.

 상황에 따라서는 생성을 피하게 할 수도 있습니다. 

 물론 사용을 하지 않았을 경우이지만 이렇게 사용을 하게 될 경우 우리는 자원을 효율적인 메모리와 함께 관리를 하게 됩니다.

 

 여기서 우리는 포인트형으로 "new"로 인해 메모리를 할당을 했기 때문에, <이 동적 메모리는 어떻게 해제를 시킬 것인가>에 대해서 생각을 가지실 것입니다.

 우리가 생각해야 할 것은 싱글톤 클래스는 맨 처음 생성된 주기로부터 - 프로그램이 종료될 때 까지 계속 생성이 되있다고 보시면 되겠습니다.

 그리고 프로그램이 생성될 때 자동으로 해체되기 때문에 그러한 문제에 대해서 고민을 하지 않아도 되며, 싱글톤 클래스의 경우에는 단 한번만 생성이 되기 때문에 메모리 릭이라던지 다른 문제에 직면할 가능성도 한없이 낮습니다.

 반면에 우리가 도중에 메무리를 해제해야 하는 경우도 생길 것 입니다.

 다이나믹 싱글톤 내부에서 자원을 동적할당을 했고, 꼭 해체를 해주어야 한다면 우리는 atexit 라는 함수를 이용하며 해체 하거나 다른 전역 객체의 소멸자를 이용하면 되겠습니다.


 1) atexit 로 Destroy 함수를 콜백으로 만드는 방법

 먼저 첫번째 방법의 경우에는 atexit라는 함수를 사용하여 삭제를 하는 방법입니다.

 -> "stdlib.h" 를 포함시켜 주어야 합니다.


 2) 전역 객체의 소멸자를 이용하여 메모리를 해제하는 방법.

 두번째 방법은 friend를 이용하여 메모리를 해제시켜주는 방식입니다.

 보통 C++에서 Friend는 프로그래밍 규격에 대해서 굉장히 안좋은 관점을 남기기 때문에 구조에 명확한 분들은 잘 사용하지 않습니다.


하지만 이 방법은 굉장히 손이 많이갈 뿐더러 생산적이지 않습니다.

이러한 명시적인 해제 방식을 하지 않는 방법이 있는데, 그 방법은 바로 static 지역 변수를 이용한 방법입니다.


스테틱 지역 싱글톤 (static local singleton)

 해당 방식은 static 변수를 지역으로 사용하여 상당히 편리하게 구성되게 하는 싱글톤 입니다.

 


 첫번째 방식의 경우에는 포인터를 사용하지 않고 주소값을 받는 방식.

 두번째 방식의 경우에는 포인터를 사용하는 방식.


 둘다 차이점은 없지만 저같은 경우에는 두번째 방식을 즐겨 사용합니다.


 지역 Static 객체로 만들경우에는 전역으로 만든 객체와는 달리 해당 함수를 처음 호출하는 시점에 초기화와 동시에 생성이 진행됩니다.

 위 객체를 한번도 사용하지 않을 경우에는 생성이 되지 않습니다. 그러면서도 static 객체이기 때문에 프로그램 종료 시까지 객체가 남아있게 되구요.

 프로그램 종료시에는 마찬가지로 자동으로 소멸자가 호출됩니다. 그러므로 소멸자에서 자원 해제를 하도록 하게되면 자원 관리도 신경 쓸 필요가 없습니다.


 하지만 이 방식에도 역시 좋은만큼 문제점이 존재합니다.

 LocalStatic 방식으로 만든 싱글톤 객체를 다른 전역 객체의 소멸자에서 사용할 시 문제가 발생하게 됩니다. (사용할 경우는 극히 드뭅니다만.)

 

 C++ 표준에서 전역 객체의 생성 순서를 명시하지 않았다고 위에서 설명을 했습니다.

 또, 소멸 순서에 대해서도 명시를 해두지 않았습니다. 즉 이것은 어떤 전역 객체가 소멸자에서 저 싱글톤 객체를 사용하려 할 때,  싱글톤 객체가 먼저 소멸했다면 문제가 발생하게 됩니다.


 이러한 문제를 해결하기 위한 방법은 Modern C++ Design 이라는 책에 서술된, (이첵은 C++의 정석입니다 완전..)

 주소 : http://book.naver.com/bookdb/book_detail.nhn?bid=128514


피닉스 싱글톤 (Phoenix Singleton)

 이 피닉스 싱글톤은 굉장히 어려운 방식으로 프로그래밍을 구현하게 되는데요. 

 그럼에도 굉장히 효율적인 방식으로 싱글톤을 구현할 수 있게 됩니다.

 이 피닉스 싱글톤은 싱글톤 참조시 해당 객체의 소멸여부를 판단하고 소멸되었다면 다시 되살리게 됩니다.

 포함 헤더는 <new> <stdlib.h> 를 해주시면 되겠습니다.


 피닉스 싱글톤은 여태까지 본거와는 다르게 상당히 소스의 양이 많습니다.

 요것에 대해서 설명하자면, 정적 개체가 소멸되면 ~PhoenixSingleton에 의해서 bDestroyed 변수가 true가 되면서 현재 소멸정보를 알려 줍니다.

 그 다음 소멸 후 GetInstance() 함수를 통해서 재 호출시, bDestroyed = false로 바꿔줌으로써 삭제가 되지 않았다고 알려줍니다.

 즉 참조할때 replacement new를 이용하여 해당 객체의 생성자를 재 호출해서 좀비(?) 마냥 되 살리는 시스템입니다.


 이것이 가능한 이유는 컴파일러는 전역 객체 소멸 시 해당 메모리를 초기화 하지 않기 때문에 메모리를 재사용하여 객체의 생성자만 재 호출하면 객체를 재 사용 할 수 있기 때문입니다. 그후 atexit() 함수에 KillPhoenix() 를 콜백으로 등록을 했기 때문에 프로그램 종료시에 Phoenix Singleton 객체의 소멸자를 이용해 리소스 해제를 할 수 있게 됩니다.


 이 방법은 굉장히 좋은 방법이지만 실제로 쓸일은 별로 없습니다.

 강력한 기능이기도 하지만, 이러한 방법보다는 먼저 싱글톤 보다는 전역변수로. 전역변수를 쓰지 않아도 될 사항이면 쓰지 않는 것을 권장 드립니다.


 그리고 요즘 최근에 C#으로 넘어가면서 굉장히 대세가 된 싱글톤이 있는데요, 그 싱글톤을 알아보도록 합시다.


Template Singleton (템플릿 싱글톤)


 이 싱글톤의 경우에는 상속만 해주면 싱글톤 기능을 활용할 수 있도록 도와줍니다.

 즉 상속을 받은 모든 객체는 싱글톤으로써 역할을 하게 되는 것 입니다.

 일반적으로는 이러한 추상적인 생성이 불가능 하기 때문에, Template을 사용하여 구현을 하게 됩니다.

 이렇게 하게되면 일일히 모든 코드에 다 싱글톤 관련해서 소스를 쓰지 않으셔도 됩니다.

 기본적으로 Template Dynamic Singleton을 사용하겠습니다.


 이렇게 해서 쓰시면 되겠습니다.

 

 이로써 싱글톤에 대해서 알아 보았습니다. 다음 디자인 패턴은 팩토리 패턴을 올리도록 하겠습니다.



댓글

  • BlogIcon 윈플.2015.03.16 16:10 신고 많이 배우고 갑니다~
  • BlogIcon VallistA2015.03.17 10:59 신고 네 ^^ 도움이 되셨으면 좋겠어요
  • 개발자2015.06.02 17:49 신고 덕분에 좋은 공부했습니다~ ^^
  • BlogIcon VallistA2015.06.03 10:02 신고 네 ㅎㅎ
    도움이 되셨으면 좋겠습니다
  • 마르잔2016.04.06 17:48 신고 아주 좋은 공부가 됬습니다. 템플릿 싱글턴이 땡기네요 감사합니다.
  • Shh14732016.04.10 18:36 신고 좋은 설명 감사합니다~ㅎㅎ
  • ctutkernel2016.08.05 19:49 신고 마지막에 설명해주신 템플릿 싱글턴의 경우에 atomic을 이용해주면
    상속 받는 class들의 thread safety도 보장할 수 있을까요?
  • BlogIcon VallistA2016.08.08 16:48 신고 글쌔요.. 테스트 한번 해보겠습니다.

    일반 싱글톤에서는 아래와 같이 적용하면되거든요.

    // in c++ 11

    static Singleton* getSingletonInstance()
    {
    if (!atomic_read(singletonInstance)) {
    mutex_lock lock(mutex);
    if (!atomic_read(singletonInstance)) {
    atomic_write(singletonInstance, new Singleton);
    }
    }
    return singletonInstance;
    }

    이렇게요..
  • BlogIcon VallistA2016.08.08 16:50 신고 근데 제가 보기에 template singleton을 atomic으로 thread safety를 보장할 수 있을 것 같습니다.
  • tjkim2016.08.10 11:45 신고 실제 singleton 사용하는 간단한 예제를 좀 부탁드립니다.
  • BlogIcon VallistA2016.08.11 00:01 신고 Singleton::GetInstance()->변수

    이런식으로 쓰시면 됩니다
    예제 제작할 시간 되면 올려놓겠습니다.

  • 지나가는 사람2016.08.16 22:19 신고 블로그 포스팅 내용 잘 보았습니다.
    디자인 패턴 관련 내용은 잘모르는데 도움이 될거 같네요.

    하지만 이곳 저곳 오타가 좀 있는거 같은데 수정해주시면 다른 분들이 참고할때 더 좋을거 같습니다.

    ==
    기본 싱글톤 (Basic Singleton)
    static Singleton instance; => static Singleton* instance;
    static Singleton& GetInstance() => static Singleton* GetInstance()
    ==

    template <typename t> T * TemplateSingleton<t>::m_pInstance = 0;
    => template <typename T> T* TemplateSingleton<T>::m_pInstance = 0;

    더 있는지는 제대로 확인은 못해보았네요.
    싱글톤 잘 배우고 갑니다~
  • BlogIcon VallistA2016.08.17 10:02 신고 아하 ^^ 오타 수정 하겠습니다 감사합니다
  • Sooooo2016.11.08 11:00 신고 정말 많은 도움 되었습니다 :) 감사합니다.
  • surcae2017.06.02 11:44 신고 이 싱글톤을 #define 을 사용한 매크로로 만들어서 사용할 수 있을까요?
  • BlogIcon VallistA2017.06.14 20:58 신고 네 사용 가능합니다.

    #define SINGLETON Signleton::instance

    이런식으로 사용하시면 될 것 같습니다.
  • 따하2017.07.12 15:28 신고 안녕하세요. 좋은 정보 잘 읽었습니다.
    다름이 아니라 좀 기초적인 부분일수도 있는건데... 부끄럽지만 질문드립니다.

    스태틱 지역 싱글톤에서 GetInstance 함수 내에 static LocalStaticSingleton ins 으로 객체를 생성하고
    ins 객체를 반환하는 부분이 궁금한데요.
    분명 저렇게 만들었다면 GetInstance 함수를 여러 번 호출하게 될텐데 호출할 때마다 static 객체가 생성되는 것이 아닌가요???
    아니면 함수 내에 static 으로 선언되어 있는 변수는 한 번 생성되어지면 그 다음부터는 static LocalStaticSingleton ins 구간을 지나도
    ins 객체가 새로이 생성되지 않고 최초 생성되어진 ins 객체가 반환이 되어지게 되는 것이지 궁금합니다.

    기초적인 부분인거 같으나 부끄럽지만 잘 모르겠어서 질문드립니다.

    감사합니다.
  • 행인2017.07.28 22:02 신고 따하님,
    함수 내에 static 으로 선언되어 있는 변수는 한 번 생성되어지면 그 다음부터는 static LocalStaticSingleton ins 구간을 지나도
    ins 객체가 새로이 생성되지 않고 최초 생성되어진 ins 객체가 반환이 되어지게 됩니다.
  • 슬픈단잠2017.09.05 21:01 신고 좋은 내용이라 생각하여 블로그에 링크로 담아갑니다.
  • 1번오타있는거같숩니다2018.03.25 20:50 신고 1번 기본 싱글톤 잘못되어있는거 같습니다...
    class Singleton
    {
    private:
    Singleton(){};
    Singleton(const Singleton& other);

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

    Singleton Singleton::instance;

    이렇게해야 밑에 설명이랑 매칭이되는거같네요 'ㅅ'
댓글쓰기 폼

VallistA

병특이 끝나서 게임에서 웹으로 스위칭한 프로그래머.
프로그래밍 정보등을 공유합니다.

자고 싶습니다. ㅠㅠ

페이스북    :: 링크
카카오톡    :: kingbye1
Github      :: 링크

궁금한점 문의 주시면 답변드리도록 하겠습니다

VISITED

Today : 144

Total : 272,555

SNS

  • 페이스북아이콘
  • 카카오톡아이콘
  • 트위터아이콘