(C++ : Design-pattern) Singleton Pattern

Posted by : at

Category : Cpp   Design-pattern


Singleton

클래스의 인스턴스는 오직 하나임을 보장

#include <iostream>
using namespace std;

class Cursor
{
    int x, y;
public:
};

int main()
{
    Cursor c1, c2;      // 커서는 오직 하나만 만들고 싶다
}
class Cursor
{
    int x, y;
private:
    Cursor() {}
public:
    static Cursor& getInstance()
    {
        static Cursor instance;
        return instance;
    }
};

int main()
{
    Cursor& c1 = Cursor::getInstance();

    Cursor c3 = c1; 
    // 복사 생성자가 호출되며 객체가 생성되어 버린다. -> 복사생성자 대입생성자를 막자.
}
class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
public:
    static Cursor& getInstance()
    {
        static Cursor instance;
        return instance;
    }
};

getInstance하는 방법에 대해서

// 방법 1. static 멤버객체로 둔다.
class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;

    static Cursor instance;
public:
    static Cursor& getInstance()
    {
        return instance;
    }
};
Cursor Cursor::instance;
// 문제. instance를 한 번도 안불러도 무조건 객체가 생성된다. -> 오버헤드의 원인이 될 수 있다.
// 방법 2. static 지역변수로 둔다.
class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
public:
    static Cursor& getInstance()
    {
        static Cursor instance;
        return instance;
    }
};
// 방법 3. new 로 객체를 생성한다.
class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
    static Cursor* pInstance;
public:
    static Cursor& getInstance()
    {
        if(pInstance == 0) pInstance = new Cursur;
        return *instance;
    }
};
Cursor* Cursor::pInstance = 0;

singleton과 멀티스레드 환경에서 동기화 문제

class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
    static Cursor* pInstance;
public:
    static Cursor& getInstance()
    {
        if(pInstance == 0) pInstance = new Cursur;
        // if를 검사하는 시점에 다른 thread에서 동시에 if를 검사한다면?
        // 객체가 2개 생성될 수 있다.
        return *instance;
    }
};
Cursor* Cursor::pInstance = 0;
#include <iostream>
#include <mutex>
using namespace std;

class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
    static Cursor* pInstance;
    static mutex m;
public:
    static Cursor& getInstance()
    {
        m.lock();       
        // 그런데 처음에야 lock이 필요하지 이후는 그냥 리턴만하는데 락을 매번 검사해야하나??
        if(pInstance == 0) pInstance = new Cursur;
        m.unlock();
        return *instance;
    }
};
Cursor* Cursor::pInstance = 0;
mutex Cursor::m;
#include <iostream>
#include <mutex>
using namespace std;

class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
    static Cursor* pInstance;
    static mutex m;
public:
    static Cursor& getInstance()
    {
        if(pInstance == 0)      // 객체 생성을 두 번 검사한다. -> 아니 if문을 두 번 타는거도 문제아니야??
        {
            m.lock();
            if(pInstance == 0) pInstance = new Cursur;
            // 그리고 멀티 스레딩 환경에서 Cursor의 생성자가 완료되기 전
            // 다른 thread에서 getInstance를 호출해 버릴 수 있다.
            m.unlock();
        }
        return *instance;
    }
};
Cursor* Cursor::pInstance = 0;
mutex Cursor::m;

아래와 같은 코딩기법을 DCLP(Double Check Locking Pattern)이라 한다. 더 상세한 내용이 궁금하면 검색해보자.

class Cursor
{
    int x, y;
private:
    Cursor() {}
    Cursor(const Cursor&) = delete;
    void operator=(const Cursor&) = delete;
    static atomic<Cursor*> m_instance;
    static mutex m_mutex;
public:
    static Cursor& getInstance()
    {
        Cursor* tmp = m_instance.load();
        if(tmp == nullptr) {
            lock_guard<mutex> lock(m_mutex);
            tmp = m_instnace.load();
            if(tmp == nullptr) {
                tmp = new Cursor;
                m_instance.store(tmp);
            }
        }
        return *tmp;
    }
};
atomic<Cursor*> Cursor::m_instance;
mutex Cursor::m_mutex;

singleton의 재사용

// 방법 1. define으로 만들어 두기
#define MAKE_SINGLETON(classname)                \
private:                                         \
    classname() {}                               \
    classname(const classname&) = delete;        \
    void operator=(const classname&) = delete;   \
public:                                          \
    static classname& getInstance()              \
    {                                            \
        static classname instance;               \
        return instance;                         \
    }                                            \

class Cursor
{
    int x, y;
    MAKE_SINGLETON(Cursor)
};
// 방법 2. 상속을 사용하기(CRTP : Curiously Recurring Template Pattern)
template<typename TYPE> class Singleton
{
protected:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
public:
    static TYPE& getInstance()
    {
        static TYPE instance;
        return instance;
    }
};

class Mouse : public Singleton<Mouse>
{

};

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links