singleton 클래스 이야기(2)
이번에는 객체의 instance()라는 메소드의 interface를 어떻게 할 것인지와 그 구현을 어떻게 하는지에 대해서 정리를 해 보겠습니다. 객체를 반환하는 방법은 reference를 반환하는 방법과 pointer로 반환을 하는 방법 2가지가 있을 수 있습니다.
static App& instance() { // (A)
}
// 혹은
static App* instance() { // (B)
}
(A), (B) 중에 어떤 interface를 선택할 것인지는 개발자의 취향의 문제이므로 무엇이 정답이라다는 것은 없습니다. 그냥 자기 입맛에 맞게 결정하면 될 것 같네요. 저의 경우에는 (A) 방식을 사용하도록 하겠습니다(instance()와 instancePtr()이라는 2가지 메소드를 동시에 제공하는 경우도 본 것 같습니다).
이제 instance()라는 메소드의 내부 구현 방식입니다. 여기에도 여러가지 방법이 있을 수 있습니다. 내부에서 관리되는 실제 객체 관리 방식에 따라 다음과 같이 구분할 수 있습니다.
-
(1) pointer로 관리하는 방법
-
(2) local object로 관리하는 방법
-
(3) global(member) object로 관리하는 방법
// (1) pointer
static App& instance() {
static App* app = nullptr;
return *app;
}
// (2) local object
static App& instance() {
static App app;
return app;
}
// (3) global(member) object
static App _instance;
static App& instance() {
return app;
}
-
(1)의 경우에는 객체를 생성시켜 주는 것과 프로그램이 완료되고 나서 해당 객체를 해제해 줘야 하는 것을 코딩으로 직접 구현을 해줘야 합니다.
-
(2)의 경우에는 lazy initialization을 하게 되므로 객체의 생성 및 해제을 프로그래머가 신경쓸 필요가 없지만, instance() 메소드에 들어갈 때마다 객체의 초기화가 이루어 졌는지 판별하는 코드가 들어가게 됩니다.
-
(3)의 경우에는(2)와 같이 객체의 생성 및 해체를 프로그래머가 신경쓸 필요가 없는 것은 마찬가지입니다. 또한 non-lazy initialization을 하게 되므로(main 함수 진입 이전에 객체의 초기화가 이루어 짐) instance() 내부에 객체 최기화 판별 여부를 구분하는 코드도 없기 때문에 (2)에서 존재하는 판별 코드도 없어 진다는 장점이 있습니다.
-
다만 (3)의 경우 메인 프로그램을 작성하면서 본 객체에 전혀 access하지 않음에도 불구하고 executable binary에 관련된 코드가 링크되어 버릴 수 있다는 단점이 존재할 수도 있습니다. 제 경험상 App 클래스 파일을 source 레벨에서 직접 포함시키는 경우에는 무조건 App 코드가 실행 파일에 링크가 되었고, App 클래스 파일을 별도의 library로 링크를 거는 경우에는 컴파일러마다 App 코드 포함 여부가 달라 졌습니다(MSVC는 미포함, GCC 계열은 포함).
아무튼 App 클래스 객체를 내부적으로 어떻게 관리를 하느냐 하는 것도 모두 장단점이 있으므로 딱히 이 방법이 좋다라는 정답은 없는 것으로 정리를 할 수가 있겠네요.
출처 : gilgil.net