singleton 패턴에서 소멸자가 private이라도 호출되는 이유는 무엇인가요?
글쓴이: vudghkzm / 작성시간: 토, 2010/05/01 - 6:01오후
아래와 같이 코드가 있다고 했을 때,
class Test { public: static Text& getInstance() { static Test t; return t; } private: Test() {}; ~Test() { std::cout << "~Test" << std::endl; }; }; int main() { Test& t = Test::getInstance(); return 0; }
프로그램을 실행하면, "~Test"가 출력되는데요.
소멸자가 private으로 선언 여부에 관계없이.. 해당 리소스가 scope가 벗어나서 소멸될때는 항상 소멸자가 호출되게 되어 있는건가요?
소멸자를 private으로 선언한 것은, 사용자가 명시적으로 delete 를 하지 못하는 용도 뿐인건가요?
Forums:
소멸자를 명시적으로 호출하는 기법이 있어서 그걸 막는 목적인 것 같네요.
소멸자를 private에 쓰는 것은 생각해본 적이 없군요.
어쨌든 program 이 종료될 때 소멸자가 호출이 되야 할텐데 이거 문제없는지는 확신이 안 드네요.
위와 같은 singleton
위와 같은 singleton 코드는 effective c++ 및 기타 곳곳에서 보여지는 예제입니다.
그런데 effective c++에서도 소멸자를 private으로 선언하는것에 대해서 특별한 설명을 해놓지 않은 것으로 기억합니다. 물론 기억이 정확하지 않을 수 잇구요.. ^^;
예전에 봤을 땐 아무 생각없이 넘어갔는데, 며칠 전 singleton 패턴을 써야할 일이 생겼을 때, 소멸자가 private인게 신기하더라구요.
적어도 gcc 4.4.1에서는 소멸자가 private이더라도 객체가 소멸될때 - 위에서 예로든 scope를 벗어날 때 - 소멸자가 호출되더군요.
암튼 저도 아직 정확한 이유를 찾지 못했는데... 정말 궁금합니다.
signleton 패턴의
signleton 패턴의 특징은 다음과 같습니다.
1. 일반적으로 생성자 및 소멸자가 public 이 아니게 두고
2. 해당 클래스의 pointer 또는 reference를 반환하는 static 함수 (위 예제에서는 getInstance()) 를 제공
이를 통해, 지정된 static 함수를 제외하고는 해당 클래스의 instance를 생성하거나 소멸을 원천적으로 차단하기 위함입니다. 흔히 전역 변수 또는 static 변수를 사용할 때의 부작용을 막기 위함입니다. 즉, 전역 객체의 경우 생성이나 소멸, 특히 생성 시점을 제어할 수 없는데, singleton 패턴을 사용하면 최초로 getInstance() 가 호출될 때 해당 객체가 생성되기 때문에, 여러 개의 singleton 객체들을 순서대로 호출함으로써, 생성 순서를 제어할 수 있게 됩니다.
그리고, getInstance() 내에서 객체를 생성하는 방법이 구현 이슈가 될 수 있는데, 위와 같이 지역 정적 객체(함수 내에서 static으로 선언되는 지역 변수)를 사용하게 되면, static 변수이기 때문에, getInstance()가 최초 호출될 때 생성될 뿐 아니라, 지역 변수이기 때문에 application이 종료될 때 정확하게 소멸자가 호출되는 것이 보장되는 장점이 있습니다. (effective c++ 을 작성한 저자 이름을 따서 Mayer's singleton 이라고도 한답니다.)
다만, 지역 정적 객체를 사용할 경우에도 multi-threading 환경 내에서 race condition 에 대한 문제점은 존재하게 됩니다. 희박하긴 하지나, 2개 이상의 thread가 동시에 getInstance()를 호출하는 경우 문제가 발생할 수 있습니다. 이는 다음과 같은 이중 검사 동기화 패턴을 통해 해결할 수 있습니다.
이중 검사 동기화 패턴은, getInstance() 를 호출할 때마다 매번 mutex를 lock/unlock 함으로 생기는 부하까지 같이 피하기 위한 방법입니다. 첫 번째 pointer 의 NULL 검사는 생성할 지 말지를 확인하기 위한 용도이고, 2 번째 NULL 검사에서 비로서 mutex를 통해 객체 생성이 한번만 일어나게 보장하는 것입니다.
대신 이렇게 하면 지역 정적 객체를 사용할 때의 장점이 없어지게 됩니다. 다시 말해, 할당된 m_instance의 소멸자를 application 종료 직전 적절한 시점에서 호출해 주어야 하는 부담을 안게 됩니다.
그런데 같이 일하는 동료가 위의 2가지를 결합한 다음과 같은 멋진 방법을 생각해 내어서 현재 이렇게 사용하고 있습니다.
여기에 mutex 사용의 경우는 RAII 패턴을 적용한 scoped lock 같은 class를 정의해서 같이 써주면 좀더 깔끔해 질 겁니다.
댓글 달기