ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 싱글싱글턴
    쥬신게임아카데미/궁금했던것 2016. 1. 6. 17:40

    http://chfhrqnfrhc.tistory.com/entry/%EC%A0%84%EC%97%AD%EB%B3%80%EC%88%98%EC%99%80-%EC%A0%84%EC%A0%81%EB%B3%80%EC%88%98

    https://kldp.org/node/121931

    말그댈 싱글은..하나

     턴은 인스턴스가 만들어지는것..

    싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어진다.

     - 어디서든지 그 인스턴스에 접근할 수 있도록 한다.

     - 클래스에서 자신의 단 하나 뿐인 인스턴스를 관리하도록 만들면 된다.

    어떠한 객체가 꼭 하나만 있어야 되는 경우 싱글톤으로 정의한 클래스 템플릿을 상속 받도록 한다.

    <싱글톤 예제>


    <예제 출력 결과>


    도대체 왜 쓸까?

    흔히 관리하는 프로그램에서 많이 쓰인다고 한다. 어느 블로그에서 "서점에서 관리자가 책들을 관리할 때 각각의 책마다 인스턴스를 두고 관리하는 것 보다 하나의 인스턴스를 갖고 하는게 낫기 때문에" - 라는 게시글을 봤다. 하나의 프로그램에서 공통적으로 쓰는 자원을 관리할 수 있기 때문이라는 의미였던 것 같다.

     

    싱글턴 패턴은 인스턴스가 하나 뿐인 특별한 객체를 만들 수 있게 해주는 패턴이다. 주로 쓰이는 용도는 아래와 같다.

     - 스레드 풀, 캐시, 대화상자, 사용자 설정, 디바이스 드라이버 등등 객체가

    전체 프로그램에서 오직 하나만 생성되어야 하는 경우

    그럼 전역 변수로 static 으로 선언해서 사용하면 되지 않을까?
    만약 전역 변수로 객체를 생성하면 어플리케이션이 실행 될 때 객체가 생성 될 것이다.

    그러나 그 객체가 자원을 많이 차지 한다면 사용도 되기 전에, 괜히 자원만 차지할 것이다.


     

     

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

     

    class CSingleton
    {
     //private로 유일한 인스턴스를 저장하기 위한 정적변수 선언
    private:
     static CSingleton Instance;

     //생성자를 private로 선언해서 CSingleton내부에서만 클래스를 만듬
     //여기서는 아래의 getInstance();
    private:
     CSingleton() { }
     CSingleton(const CSingleton& other) {}

     //클래스의 인스턴스를 여기 즉, CSingleton내부에서 만들어서 리턴
    public:
     static CSingleton& getInstance()
     {

      return Instance;
     }
    };

     


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


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

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

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

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

     

     

    class CSingleton
    {
     //private로 유일한 인스턴스를 저장하기 위한 정적변수 선언
    private:
     static CSingleton* Instance;

     //생성자를 private로 선언해서 CSingleton내부에서만 클래스를 만듬
     //여기서는 아래의 getInstance();
    private:
     CSingleton() { }
     CSingleton(const CSingleton& other) {}
     ~CSingleton() {}

     //클래스의 인스턴스를 여기 즉, CSingleton내부에서 만들어서 리턴
    public:
     static CSingleton* getInstance()
     {
      if(Instance == NULL)
      {
       Instance = new CSingleton;
       return Instance;
      }
     }
    };

    CSingleton* CSingleton::Instance = NULL;

     최초 GetInstance()를 호출을 하게 되는 시점에 생성이 됩니다.

    위에 그냥 일반 static의 경우 처음부터 메모리를 잡고 생성되어 있지만 이건 포인터로

    new해야지만 생성된다는 것!!! 생성시점을 내가 정한다!

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

     

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

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

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

     

     


    다이나믹 싱글톤 내부에서 자원을 동적할당을 했고,(이러면 메모리 릭이 부담이 되지??한두개가 아니라 무거운게 있다면..)

    꼭 해체를 해주어야 한다면 우리는 atexit 라는 함수를 이용하며 해체 하거나 다른 전역 객체의 소멸자를 이용하면 되겠습니다.

     

    1. atexit로 Destory함수를 콜백으로 만드는 방법!

    atexit는 프로그램 소멸자 함수(?) 를 등록하는 함수인데, 종료직전 그러니까

    꼭 해야할 작업이 있을때 함..

    최대 32개만 등록할 수 있다고 한다.(최후로 등록된 함수가 가장 먼저 실행 스택이네??)

    종료처리 함수는 인수를 취할 수 없다 리턴값도 없는 void func(void) 형이어야 한다.

    원형은 int atexit(atexit_t func); 인데..

    typedef void_Cdecl(*atexit_t)(void); 으로 자료형이 재정의 되어 있음..맞지? void func(void)형

    그리고 stdlib.h 헤더파일이 필요함..

    그래서 아래처럼 다시 작성됨..

    class CSingleton
    {
     //private로 유일한 인스턴스를 저장하기 위한 정적변수 선언
    private:
     static CSingleton* Instance;

     //생성자와 복사생성자를 private로 선언해서 CSingleton내부에서만 클래스를 만듬
     //내부에서 만든다는건 여기클래스기준으로는 아래의 getInstance(); 이야기함
    private:
     CSingleton() { }
     CSingleton(const CSingleton& other) {}
     ~CSingleton() {}

     //atexit함수를 사용하기 위해 선언된것..메모리 해제!
     static void Destory()
     {
      delete Instance;
     }
     //클래스의 인스턴스를 여기 즉, CSingleton내부에서 만들어서 리턴
    public:
     static CSingleton* getInstance()
     {
      if(Instance == NULL)
      {
       Instance = new CSingleton;
       atexit(Destory);
       return Instance;
      }
     }
    };

    CSingleton* CSingleton::Instance = NULL;

     

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

    ..잘안한다고 한다 friend를 이용해서 특정한 static으로 만들어진 전역객체를 정하고..

    그 객체의 소멸자에서(friend하면 내부에 접근하니까..) Instance를 해제함..

     

    3. 제일 좋은? 스택틱 지역 싱글톤..??

    //1. 첫번째 방식
    class LocalStaticSingleton
    {
    private:
        LocalStaticSingleton();
        LocalStaticSingleton(const LocalStaticSingleton& other);
        ~LocalStaticSingleton();
     
    public:
        static LocalStaticSingleton& GetInstance()
        {
            static LocalStaticSingleton ins;
            return ins;
        }
    };
     
    //2. 두번째 방식
    class LocalStaticSingleton
    {
    private:
        LocalStaticSingleton();
        LocalStaticSingleton(const LocalStaticSingleton& other);
        ~LocalStaticSingleton();
     
    public:
        static LocalStaticSingleton* GetInstance()
        {
            static LocalStaticSingleton ins;
            return &ins;
        }
    };

     

     둘다 차이점은 없지만 저같은 경우에는 두번째 방식을 즐겨 사용합니다.
     지역 Static 객체로 만들경우에는 전역으로 만든 객체와는 달리 해당 함수를 처음 호출하는 시점에 초기화와 동시에 생성이 진행됩니다. 

    오오....이건 아까 위애서 걱정한 생성시기에 대한 부담이 줄어든다..!!

    그리고 객체를 한번도 사용하지 않을 경우에는 생성이 되지 않고..static 객체이니까

    프로그램 종료시까지 객체가 남아있게 된다!

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

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

     


    문제발생 참고

     

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

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

     

    정적객채의 초기화와 소멸에 관련된 문제인데요.

    정적객채는 main이 실행되기 전에 메모리에 올라오게 되는데 구조는 스택 구조를 가집니다.
    비지역정적객체의 경우 객채의 초기화 순서는 정의되어 있지 않습니다.(Effective C++ 5장 참고).
    때문에 이 예제에서는 싱글턴이 비지역정적객채로 되어 있기때문에 초기화는 컴파일러 맘이라서 운이 좋아서
    Log 인스턴스가 먼저 생성이되고 App가 생성되면 문재가 없을거구요. 반대라면 문재가 되겠죠.
    정적객채를 크로스로사용할 때는 초기화 시점을 조절을 잘 해줘야 문재가 발생하지 않습니다.
    때문에 초기화 시점을 조절할수있게 비지역정적객채를 지역정적객채로 변경을 하고 Log인스턴스를 명시적으로
    먼저 생성하게 되면 문재가 없을듯합니다.

     

    음... 우선 정적 객채에 대한 정의가 필요한 시점인것 같습니다.
    정적객채란 1. 전역객체, 2, 네임스페이스 유효범위에서 정의된 객체, 3, 클래스 안에서 static 으로 정의된 객체
    4, 함수 안에서 static으로 선언된 객체, 5. 그리고 파일 유효범위에서 static으로 정의된 객체
    이렇게 다섯가지로 나뉩니다. (effective c++ 4장 참조).
    이중 4번째인 함수 안에서 정적 객채로 선언된 객채는 이들중 일컬어 지역정적 객채라고 하고 나머지는 비지역정적객채라고 합니다.
    정적객채중유일하게 필요한 시점에 인스턴스가 일어나는 객채입니다.

     

    이러한  전역/정적 객체의 골칫거리가 하나 있는데, 바로 전역/정적 객체의 생성/소멸 순서이다. 어느 전역/정적 객체가 먼저 생성되고 나중에 생성될지 알 수 없기 때문에, 한 전역/정적 객체의 생성자에서 다른 전역/정적 객체를 참조하게 되면, 예상치 못한 오류를 만날 수 있다.

     

    ??main함수가 static 변수보다 먼저 종료되어 소멸된다고?? 

     



     

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


    Template Singleton (템플릿 싱글톤)


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

     (상속때문에 안에서 new한다..아니면 그냥 지역정적변수겠지만..)

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

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

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

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

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    template < typename T >
    class TemplateSingleton
    {
    protected:
        TemplateSingleton()
        {
                 
        }
        virtual ~TemplateSingleton()
        {
     
        }
     
    public:
        static T * GetInstance()
        {
            if (m_pInstance == NULL)
                m_pInstance = new T;
            return m_pInstance;
        };
     
        static void DestroyInstance()
        {
            if (m_pInstance)
            {
                delete m_pInstance;
                m_pInstance = NULL;
            }
        };
     
    private:
        static T * m_pInstance;
    };
     
    template <typename t> T * TemplateSingleton<t>::m_pInstance = 0;
     
    //!< 사용법
    class CObject : public TemplateSingleton<cobject> //우와 상속한다!
    {
    public:
        CObject();
        ~CObject();
    };

     

     

    '쥬신게임아카데미 > 궁금했던것' 카테고리의 다른 글

    정리  (0) 2016.01.16
    메모리값 관련.의미..  (0) 2015.12.29
    메모리 구조관련(미해결..)  (0) 2015.12.29
    소수점을 정수로 캐스팅 할때  (0) 2015.11.26
    전방선언과 헤더파일 포함할때..  (1) 2015.11.24
Designed by Tistory.