Thinking Different




무분별한 객체생성을 방지하고, 1개의 객체만 생성하여 이용하는 프로그램 코딩에 유용하게 적용할 수 있다.


아래 6가지 경우를 통해서 싱글턴 패턴 생성에 대해서 알아 보도록 하자

1. 동적 싱글턴 패턴

1
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
class Singleton
{
private:
    Singleton() {}
    ~Singleton() {}
    Singleton(const Singleton& s) {}
 
public:
    static Singleton* GetInstance()
    {
        if (m_pInstance == nullptr)
        {
            m_pInstance = new Singleton;
        }
 
        return m_pInstance;
    }

    // 명시적 해제 함수 지정
    void destroy() 
    {
          if(m_pInstance != nullptr)
          {
               delete m_pInstance;
               m_pInstance = nullptr;
          }
    }
 
private:
    static Singleton* m_pInstance;
};

 Singleton* Singleton::m_pInstance = nullptr;

//------------------------------------------------------------------
// Main
int _tmain(int argc, _TCHAR* argv[])
{
    Singleton* pSingleton = Singleton::GetInstance();

     // ...  다 사용한 다음 main함수 종료직전 명시적으로 해제
     pSingleton->destroy();

    return 0;
}
 


문제가 없는듯 보이는데 그러나 이 패턴에는 문제점이 존재한다. 바로 new로 생성한 객체의 소멸을 보장받지 못하는 것이다. (리소스 누수현상 발생)

(static 변수의 경우 main함수가 static 변수보다 먼저 종료되어 소멸을 보장받지 못하므로 명시적으로 main함수 종료직전 소멸을 호출하여 사용자가 직접 해제해주어야 됨)

이러한 누수현상에 대처하기 위해서 동적 생성이 아닌 정적 생성 사용을 예로 들 수 있다.


2. 정적 싱글턴 패턴

1
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
class Singleton
{
private:
    Singleton() {}
    ~Singleton() {}
    Singleton(const Singleton& s) {}
 
public:
    static Singleton* GetInstance()
    {
        return &m_pInstance;
    }
 
private:
    static Singleton m_pInstance;
};
 
//------------------------------------------------------------------
// Main
int _tmain(int argc, _TCHAR* argv[])
{
    Singleton* pSingleton = Singleton::GetInstance();
 
    return 0;
}
 


정적 객체를 생성하여 프로그램 종료시 객체를 해제할 필요가 없어졌다.


3. axexit() 함수를 이용한 동적 객체 자동 반환 싱글턴 패턴

atexit() 함수에 대하여

atexit 함수는 프로그램 종료시에 (main함수 종료 직전) 실행되며, 인자는 함수 포인터를 가진다.

1
2
3
int atexit(
   void (__cdecl *func )( void )
);
 

이 정의에서 알수 있듯이 atexit 함수는 인수를 취할 수 없고, 리턴값도 없는 void func(void)형이어야 한다. 또한 이 함수는 32개까지 등록될 수 있으며 최후로 등록된 함수가 가장 먼저 실행된다.

atexit함수를 이용하면 프로그램 종료 직전에 내가 원하는 작업을 할 수 있는 기능을 만들어 줄 수가 있게 된다. 


아래 코드를 보자.

1
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
#include "stdafx.h"
 
class Singleton
{
private:
    Singleton() {}
    ~Singleton() {}
    Singleton(const Singleton& s) {}
 
private:
    static void destroy() 
     { 
           if(m_pInstance != nullptr) 
                 delete m_pInstance;
      }  // 객체 소멸
 
public:
    static Singleton* GetInstance()
    {
        if (m_pInstance == NULL)
        {
            m_pInstance = new Singleton();
            atexit(destroy);    // 프로그램 종료 직전호출
        }
 
        return m_pInstance;
    }
 
private:
    static Singleton* m_pInstance;
};
 
Singleton* Singleton::m_pInstance = NULL;
 
//------------------------------------------------------------------
// Main
int _tmain(int argc, _TCHAR* argv[])
{
    Singleton* pSingleton = Singleton::GetInstance();
 
    return 0;
}
 


자 위 코드는 동적 객체를 생성하고 atexit()함수를 이용하여 main()함수 종료 전에 객체를 delete하도록 하였다. 이렇게 하면 객체 생성 및 해제를 아주 깔끔하게 해결 할 수 있다.




4. 템플릿을 이용한 싱글턴 패턴 구현

1
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
44
45
46
47
48
49
50
51
#include "stdafx.h"
 
//------------------------------------------------------------------
// template singleton pattern
template<typename T>
class Singleton
{
protected:
    Singleton() {}
    virtual ~Singleton() {}
    Singleton(const Singleton& s) {}
 
private:
    static void destroy() 
     { 
           if(m_pInstance != nullptr) 
                 delete m_pInstance;
      }  // 객체 소멸
 
public:
    static T* GetInstance()
    {
        if (m_pInstance == NULL)
        {
            m_pInstance = new T();
            atexit(destroy);    // 프로그램 종료 직전호출
        }
 
        return m_pInstance;
    }
 
private:
    static T* m_pInstance;
};
 
template <typename T> T* Singleton <t>::m_pInstance;
 
//------------------------------------------------------------------
// test code
 
class 테스트 : public Singleton<테스트>
{
public:
    void 싱글턴_테스트() { cout << "템플릿 싱글턴 테스트 합니다." << endl; }
};
 
//------------------------------------------------------------------
// Main
int _tmain(int argc, _TCHAR* argv[])
{
    테스트::GetInstance()->싱글턴_테스트();
 
    return 0;
}
 




5. 멀티 스레드 기반에서 싱글턴 패턴 구현 (volatile 활용)

1
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
template<typename T>
class CSingleton : public CMultiThreadSync<T>
{
protected:
    CSingleton() {}
    virtual ~CSingleton() {}
    CSingleton(const CSingleton& s) {}
 
private:
    static void destroy() 
     { 
           if(m_pInstance != nullptr) 
                 delete m_pInstance;
      }  // 객체 소멸
 
public:
    static T* GetInstance()
    {
        if (m_pInstance == NULL)
        {
            CThreadSync cs;
 
            m_pInstance = new T();
            atexit(destroy);    // 프로그램 종료 직전호출
        }
 
        return m_pInstance;
    }
 
private:
    static T* volatile m_pInstance;
};
 
template <typename T> T* volatile CSingleton <T>::m_pInstance;
 

volatile 를 활용한 싱글턴 패턴 코드로써 개인적으로 추천하지는 않는다.



6. 스마트 포인터 shared_prt 을 활용한 싱글턴 패턴

스마트 포인터와 shared_ptr 이란?

  • 스마트 포인터 : heap 메모리에 new, malloc으로 할당된 경우 자동으로 free, delete(소멸) 해주는 똑똑한 포인터 (auto_ptr, shared_ptr, weak_ptr 등이 있다)
  • shared_ptr : 참조 횟수(reference counting)에 따른 메모리 할당 해제를 수행한다. 즉, 최초 객체를 할당시 실질적으로 메모리에서 할당받고, 그 다음부터는 객체 복사시 실제 복사는 이루어지지 않고 카운팅을 올려서 관리하는 기법이다. 또한 카운팅이 0가 되었을 경우 메모리를 자동 해제하게 된다.
  • thread_safe 한 자료형이므로 멀티스레드상에서 사용하여도 안전하다. (주의!! : 레퍼런스 카운팅에 대해서만 안전성을 보장받는다, 참조하고 있는 객체에 대한 동기화 보장은 해주지 않으므로 따로 동기화 처리를 해주어야 한다.)

shared_ptr<자료형> 이름(사이즈);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T>
class CSingleton
{
public:
    static T* GetInstance()
    {
        std::call_once(m_onceFlag, [] {
            m_pInstance.reset(new T);
        });
 
        return m_pInstance.get();
    }
 
private:
    static std::shared_ptr<T> m_pInstance;
    static std::once_flag m_onceFlag;
};
template<typename T> std::shared_ptr<T> CSingleton<T>::m_pInstance = NULL;
template<typename T> std::once_flag CSingleton<T>::m_onceFlag;