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>
{
};