singleton 클래스를 디자인하고 만드는 것에 대해 여러 글을 읽어 보았습니다. 그래서 이번에 정리 차원에서 이래 저래 검토해 본 "singleton 클래스 이야기"라는 글을 써 볼까 합니다.  본 글에서는 App라는 클래스(application 혹은 process의 정보를 제공하는 클래스)를 예를 들면서 설명을 해 보겠습니다.




위키피디아에서는 singleton을 다음과 같이 정의하고 있습니다. 즉 쉽게 말해서 객체(instance)가 하나뿐인 클래스라고 쉽게 생각하면 됩니다.


In software engineering, the singleton pattern is a design pattern used to implement the mathematical concept of a singleton, by restricting the instantiation of a class to one object. 




여기에서 몇가지 전제 조건이 생기게 됩니다.


(A) instance가 함부로 생성되지 않도록 constrcutor 및 destructor를 public으로 선언하지 않고 private로 선언한다.


(B) instance의 복사가 허용되지 않도록 copy constructor 및 assign operator를 public으로 선언하지 않고 private로 선언한다.


(C)  instance의 interface를 제공하기 위해 instance()라는 메소드를 public으로 제공한다.




상기 조건을 이용해서 App라는 클래스를 만들어 보겠습니다.


class App
{
private: // private constructor and destructor prevent creating and deleting object. // (A)
  App();
  virtual ~App();

private: // private copy constructor and assign operator prevent copying object. // (B)
  App(const App&);
  const App& operator = (const App&);

public: // public instance methods supports class reference access. // (C)
  static App& instance()
  {
    static App app;
    return app;
  }
};


(A)를 private(protected가 아닌)으로 선언을 하는 것은 App 클래스의 상속을 허용하지 않기 위함입니다. singleton 클래스를 상속하면 안되는 이유는 추후에 다시 설명하겠습니다.


(B)를 public으로 선언하지 않은 이유는 객체의 복사를 방지하기 위해서입니다. (B)를 private으로 하느냐 protected로 하느냐는 별 의미가 없습니다. 어차피 클래스 상속이 (A)에 의해 차단이 되므로.


(C)에서는 app라는 object를 static local로 선언을 하였습니다. static local로 선언하는 것과 static global(member)로 선언하는 방식 2가지가 있을 수 있는데, 각각 장단점이 있습니다. 이는 추후에 자세한 설명을 하도록 하겠습니다.




설계된 App class를 이용해서 클래스에 접근을 해 보도록 하겠습니다. 의도하는 바대로 compile error가 나면서 객체의 외부 생성 및 복사를 방지함을 알 수가 있습니다.


void testA()
{
  App app; // 'App::App' : cannot access private member declared in class 'App'
           // 'App::~App' : cannot access private member declared in class 'App'
};

void testB(App* app1, App* app2)
{
  *app1 = *app2; // 'App::operator =' : cannot access private member declared in class 'App'
}

void testC()
{
  App& app = App::instance(); // compile ok
}




참고로  boost::noncopyable 이라는 클래스를 상속받아서 상기 요구 사항중 (B)를 해결할 수도 있습니다.


#include <boost/noncopyable.hpp>

class App : boost::noncopyable
{
private: // private constructor and destructor prevent creating and deleting object. // (A)
  App();
  virtual ~App();
public:
  static App& instance()
  {
    static App app;
    return app;
  }
};