singleton 패턴에서 소멸자가 private이라도 호출되는 이유는 무엇인가요?

vudghkzm의 이미지

아래와 같이 코드가 있다고 했을 때,

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 를 하지 못하는 용도 뿐인건가요?

winner의 이미지

소멸자를 private에 쓰는 것은 생각해본 적이 없군요.
어쨌든 program 이 종료될 때 소멸자가 호출이 되야 할텐데 이거 문제없는지는 확신이 안 드네요.

vudghkzm의 이미지

위와 같은 singleton 코드는 effective c++ 및 기타 곳곳에서 보여지는 예제입니다.
그런데 effective c++에서도 소멸자를 private으로 선언하는것에 대해서 특별한 설명을 해놓지 않은 것으로 기억합니다. 물론 기억이 정확하지 않을 수 잇구요.. ^^;

예전에 봤을 땐 아무 생각없이 넘어갔는데, 며칠 전 singleton 패턴을 써야할 일이 생겼을 때, 소멸자가 private인게 신기하더라구요.

적어도 gcc 4.4.1에서는 소멸자가 private이더라도 객체가 소멸될때 - 위에서 예로든 scope를 벗어날 때 - 소멸자가 호출되더군요.

암튼 저도 아직 정확한 이유를 찾지 못했는데... 정말 궁금합니다.

philnet의 이미지

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()를 호출하는 경우 문제가 발생할 수 있습니다. 이는 다음과 같은 이중 검사 동기화 패턴을 통해 해결할 수 있습니다.

class Test {
public:
    static Text& getInstance() {
	if ( !m_instance )
	{
		m_mutexForCreation.Lock();
		if ( !m_instance )
		{
			m_instance = new Test;
		}
		m_mutexForCreation.Unlock();
	}
 
	return m_instance;
    }
 
private:
    Test() : m_instance(NULL) {};
    ~Test() { std::cout << "~Test" << std::endl; };
    static Test* m_instance;
    static Mutex m_mutexForCreation;
};

이중 검사 동기화 패턴은, getInstance() 를 호출할 때마다 매번 mutex를 lock/unlock 함으로 생기는 부하까지 같이 피하기 위한 방법입니다. 첫 번째 pointer 의 NULL 검사는 생성할 지 말지를 확인하기 위한 용도이고, 2 번째 NULL 검사에서 비로서 mutex를 통해 객체 생성이 한번만 일어나게 보장하는 것입니다.

대신 이렇게 하면 지역 정적 객체를 사용할 때의 장점이 없어지게 됩니다. 다시 말해, 할당된 m_instance의 소멸자를 application 종료 직전 적절한 시점에서 호출해 주어야 하는 부담을 안게 됩니다.

그런데 같이 일하는 동료가 위의 2가지를 결합한 다음과 같은 멋진 방법을 생각해 내어서 현재 이렇게 사용하고 있습니다.

class Test {
public:
    static Text& getInstance() {
	if ( !m_instance )
	{
		m_mutexForCreation.Lock();
		if ( !m_instance )
		{
			static T obj;
			m_instance = &obj;
		}
		m_mutexForCreation.Unlock();
	}
 
	return m_instance;
    }
// 나머지 동일
};

여기에 mutex 사용의 경우는 RAII 패턴을 적용한 scoped lock 같은 class를 정의해서 같이 써주면 좀더 깔끔해 질 겁니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.