C++ 싱글턴 템플릿 혹시 어떨까요?

jeongheumjo의 이미지

싱글턴이라는 놈을 써보았습니다.
그런데 너무 많이 사용하다보니 템플릿으로 만들어서 싱글턴 성격을 갖을 필요가 있는 클래스에 상속해서 쓰면 좋겠다 싶었습니다.
그런데 잘 찾아지지 않네요.. 더군다나 kldp 와 외국 사이트들 중에서도 싱글턴이 나쁘다 혹은 싱글턴은 상속할 때 문제가 많다 등등의 말들이 많습니다.

http://translate.google.co.kr/translate?hl=ko&sl=en&tl=ko&u=http://www.daniweb.com/software-development/cpp/threads/251676&anno=2

이것은 좋은 샘플은 아니예요.. 누군가가 자신의 코드를 올려본 것인데 저도 나름 괜찮아 보였는데 실상 이와 같이 템플릿으로 싱글턴을 쓰는 코드를 어느 책에서도 어느 믿을만한 사이트에서도 잘 찾아지지 않네요..

싱글턴은 정말 GoF 책이나 헤드퍼스트 에서처럼 단순하게 Final 클래스(자바에서... C++ 에서는 그냥 자식클래스)에서만 쓸 수 있는 것일까요? 싱글턴 인터페이스(추상클래스)를 만들 수는 없을까요?

kukyakya의 이미지

C++에서 derived class의 생성자의 publicity를 강제할 방법이 없어서 상속만으로 싱글턴 패턴을 적용하기는 힘들 것 같습니다.

저도 올려주신 코드랑 비슷하게 만들어서 쓰고는 있는데, 깜박하고 default 생성자가 생성되도록 남겨두거나 하면 스택에 인스턴스를 만드는 실수를 막을 길이 없네요.

jeongheumjo의 이미지

그게 싱글턴의 디자인인 것 같아서요..
상속을 하려니 복잡해지네요..

winner의 이미지

제가 singleton을 싫어하는 이유는 전역변수와 같은 이유입니다. 전역변수가 필요하다면 쓰는 것처럼 singleton 역시 필요하면 사용합니다만 저는 대부분 필요성을 느끼지 못했고, 오히려 기존 source에서 singleton이 있으면 설계실수를 의심했습니다.
또한 singleton 으로 만들어져서 별도 객체가 필요함에도 같은 객체를 참조하는 bug도 봤습니다. 덕분에 singleton에 전역접근하는 50개 이상의 class의 생성자를 모두 고치기도 했습니다. 지금 생각해보면 별도의 객체(singleton이었던)를 참조해야 하는 50개 이상의 class의 객체들의 최상위 객체에 접근할 수 있는 전역함수가 있었을 것 같고, 그를 통해 singleton class에서 객체생성과 반환을 할 때 최상위 객체를 id로 판단하는 방법을 활용하여 생성자의 interface는 그대로 내버려둘 수도 있었을 것 같기는 합니다만 별로 후회스럽지 않습니다. 만일 그렇게 했다면 그 singleton class는 다시 역으로 그 해당 최상위 객체의 class에 의존하게 되었겠죠.
하지만 회사에서 제가 붙잡고 있는 source는 coder에게 source 품질을 요구하지 않는 경우 source가 이렇게 꼬일 수도 있다는 생각이 드는 것이라 이런 제 작업이 의미있는 것인지 의문이 들기는 합니다.

제가 뜯어 고쳤던 것은 DB를 접근하는 class 였습니다. DBMS 접근은 단일 접근을 하는 것이 성능과 일관성 측면에서 좋을 것이고, 따라서 singleton을 쓸 것을 고려해야 한다라는 guideline을 별 생각없이 따랐기 때문에 발생한 문제였던 것이죠. 전역접근객체가 별도의 객체가 필요해짐에 따라 매개변수화 할 때 괴로워질 수 있는 것처럼 singleton 역시 그런 위험성을 가지고 있습니다. 물론 전역접근이 허용하는 설계가 전체적인 복잡도를 제어하는데 올바른 방법일 수도 있습니다. 현재 제가 singleton을 피하는 가장 큰 이유는 test 때문입니다.

jeongheumjo의 이미지

Meyers Singleton 을 찾아보니까 누가 말하길 싱글턴 객체의 생성을 처음 할 때 가급적 안전한 시점에 미리 해두라고 했는데, 그만큼 멀티쓰레드에서 고민스러운 부분이 많은 것 같습니다.
그리고 생각보다 싱글턴을 만드는 방법이 참 다양한 것 같습니다. 책에서의 방법 말고도요...
아 그리고 저는 지금 코드에서 싱글턴은 그냥 쓰려 합니다. 조심하면서 사용하면 문제 없을 것 같거든요.. 그리고 이미 많이 만들어서 원복하기도 부담스럽고...
싱글톤 쓰지 않는게 좋다는 조언 감사합니다. 잘 참고해야겠어요..

winner의 이미지

Design Pattern은 언어와 연관된 부분이 많습니다. GoF도 이런 이야기를 책에서 설명했었습니다. 어떤 언어에서 필요하다고 생각되는 pattern이 다른 언어에서는 그런 pattern 없이 문제가 없다고 말이죠. GoF가 pattern이라는 용어를 쓰기 전에는 사실 관용구(idiom)이라는 용어가 사용되었는데 GoF가 이것을 pattern으로 재정립하면서 언어와 구현에 관계없이 추상적으로 설명하려고 했었습니다만 언어에 종속적이라는 것은 피할 수 없는 한계였죠.
Pattern에 대한 것이 추상적으로 발전하면서 GoF pattern을 설계패턴(Design Pattern)이라는 용어로 제한하고 좀더 추상적인 객체지향원칙이나(GRASP, SOLID) 혹은 아키텍처에 대해서 의논이 되기도 했습니다.

어쨌든 pattern은 추상적 문제정의와 해결책에 대한 이야기인데 이것을 다시 구체화시키는 것에 대해서는 또한번 혼란이 있곤 합니다. 그래서 다양한 이야기가 있는 것이고요. Singleton이 말이 많은 이유 중 하나는 singleton이 가장 간단하게 설명(혹은 구현)할 수 있기 때문이라고 하죠. 그래서 간단하게 design pattern을 이야기하는 column에서 꼭 등장하곤 하고요. 저는 이런 상황을 보면 그냥 옛날의 관용구라는 용어가 design pattern보다 훨씬 생산적이지 않은가 합니다.

마지막으로 원래 복잡도는 제어(통제)하는 것이지 없앨 수 있는 것은 아닙니다. GoF도 pattern을 설명할 때 각 pattern의 약점을 이야기하는 것을 빠뜨리지 않았습니다. 어떤 방법도 복잡도를 한방에 날려버릴 은탄환은 되지 못합니다. 설계에 대한 고민은 적당히 하고, coding을 꾸준히 하면서 설계개선에 대한 통찰을 얻을 수 있도록 노력하는 것이 좋다고 생각합니다. 어떤 설계도 천년만년 영속할 수 있는 source를 보장하지는 못합니다.

emptynote의 이미지

singleton은 c++ 만의 문제가 아닌듯합니다. 자바도 어렵습니다.

자바의 경우 성능에 생각없이 synchronized 라는 키워드를 넣는다면 그나마 구현이 쉽지만,

성능을 위해서 그것을 빼는 방법을 찾을땐, thread safe 라는 거대한 장벽과 마주해야 합니다.

synchronized 라는 키워드를 넣는다고 thread safe 함을 보장하는것은 아니기에,

결국은 말씀하신 singleton test의 어려움은 정말로 공감합니다.

가끔 이문제에 대해서 생각해 보면 철학적인것 같습니다.

전역 변수로 1번 초기화한거 새롭게 다시 생성하지 말고 쓰라고 팀원들 한테 맡기는 자율 사회와

팀원을 못 믿기에 강제적으로 1번만 생성하도록 만드는 singleton 을 사용해야 하는 통제된 사회

개인적으로 어느 사회에서 살거냐 묻는다면 당연히 자율 사회에서 살고 싶다고 할텐데도,

개인적으로 제가 만든 프로그램에서 singleton 도배를 했네요.

저는 독재자인듯합니다. 물론 지금은 진지하게 삭제를 검토중입니다.

테스트 부담과 성능 측면에서 보자면

모범적인 자율사회가 통제된 사회 보다 훨씬 좋지요.

다만, 모범적이지 않는 자율사회는 통제된 사회보다 못할수도 있습니다.

현실 세계나 컴퓨터 프로그래밍 세상이나 참 닮았다는 생각이 듭니다.

다 사람들이 모여서 하기때문에 그런걸까요?

예외) servlet/jsp 에서 connection pool을 servlet/jsp 엔진에서 미 제공하고
또 사용자가 정의한 전역 class를 올리수가 없다면,
전역 1번 초기화후에 servlet/jsp(=> thead)에서 호출됨을 보장해야 하는데
그것이 불가능하므로 부득이 singleton을 고려해야 할듯하네요.
이슈) 전역 변수 자체는 별로 환영 받을 일은 아니죠. 물론 singleton 도 근본은 전역 변수지만요.

philnet의 이미지

Singleton을 사용하면서 (unit) test를 용이하게 수행할 수 있습니다.

즉, singleton clas가 MySingleton class라고 한다면,
1. MySingletonImp 추상 클래스를 작성
2. MySingleton class는 생성자에서 MySingletonImp instance(의 pointer 또는 reference)를 인자로 전달 받아 이를 멤버 변수로 저장.
3. MySingleton의 모든 (public) 멤버 함수는 MySingletonImp instance에게 전달(위임)

물론 MySingleton class는 주요한 함수들을 순수 가상 함수로 선언하고, unit test 시에는, TestMySingletonImp 객체를 생성해서 이를 MySingleton 초기화 시에, 넘겨 주는 식으로 사용하는 것이지요.

켄트백의 TDD 책을 보면 singleton 을 피하라는 얘기가 나오지만, 특정 class의 생성 자체를 제어할 수 있다는 singleton의 매력을 포기하기도 쉽지 않습니다.

winner의 이미지

제 생각은 이렇습니다. 흔히 class로 표현되는 singleton은 전역객체의 하나라고 볼 수 있습니다. Class와 객체를 구분하지 않고, class를 객체의 meta 객체로 보는 언어에서는 class도 그냥 객체의 하나일 뿐입니다.
통상적으로 singleton을 통해 객체생성을 제한하는 방식은 사실 singleton class로 표현된 전역객체에 접근할 때 적절한 통제를 하는 것인데 특히 thread safe 문제를 논의를 할 때에는 message queuing이나 동시성 문제를 해결하기 위한 lock으로 설명하는 것이 바람직합니다. Singleton으로 이 문제를 논의하는 것은 사람들이 논의를 할 때 활용할 수 있는 적절한 metaphor가 될 수 없습니다.
Class와 객체의 관계를 논의하는 것은 어디까지나 객체의 생성을 논의하는 것인데 틀(class)을 만들어 놓고, 한번만 활용한다는 것은 틀의 활용가치가 떨어진다는 이야기밖에 되지 못합니다.
Singleton class를 통해서 동시성 문제와 관련된 사항을 해결하는 것은 탁월한 편법 내지는 꼼수는 될 수 있을지언정 문제정의와 해결을 위한 설명에는 매우 부적합하다고 봅니다.

jeongheumjo의 이미지

winner 님의 아래 말씀에 저도 동감합니다.

 특히 thread safe 문제를 논의를 할 때에는 message queuing이나 동시성 문제를 해결하기 위한
 lock으로 설명하는 것이 바람직합니다. Singleton으로 이 문제를 논의하는 것은 사람들이 
논의를 할 때 활용할 수 있는 적절한 metaphor가 될 수 없습니다.

싱글턴에서의 동시접근에 대한 문제를 보았을 때 그 문제는 싱글턴 구현 방법에서 해결하는 것 보다는 별도의 멀티쓰레드 접근 기법으로 해결해야 한다는 것이 제 짧은 결론이었거든요. 제가 읽고있는 첫번째 디자인패턴 책인 헤드퍼스트에서도 동시접근 문제 해결의 중요성과 여러가지 방법을 자바언어로 설명하고 있습니다. 하지만 그런 방법들은 패턴과는 분리해서 설명하는 것 같았고요, 상황에따라 해결방법도 다르다고 했던 것 같습니다. 더군다나 헤드퍼스트 디자인패턴의 C++ 오픈소스 프로젝트의 코드를 봐도 동시접근에 대한 해결책은 싱글턴에서 고려하지 않았습니다. 구현된 싱글턴 클래스를 쓸 때 놓치지 말고 고려해서 해결해야한다는 것이 제가 받은 인상이었습니다. 그리고 제가 생각하고 있는 가장 안전한 해결책은 프로그램의 안전한 시점(주로 초기화 과정)에서 미리 필요한 싱글턴 객체들의 생성을 해두는 것입니다. 이렇게 되면 그냥 전역변수와 다를게 뭐가 있나 싶습니다만...
아마 이 부분때문에 개발자들 사이에서 의견이 분분한 것 같습니다.

jeongheumjo의 이미지

일전에 이 게시판 쓰레드에서의 논의 이후 저는 반심반의 하면서 싱글턴을 사용했습니다.
동기화 문제는 안전한 시점에 객체를 생성하는 방식으로 해결했구요..
남발하지 않으면서 필요한 케이스들마다 잘 적용한 것 같습니다.
그러면서도 그동안 싱글턴이 정말 필요한 이유(전역 객체를 사용하는 것 대비)를 찾지 못하였었습니다.

그런데 오늘 그 유용함을 발견했어요..

제 결론은 모듈화된 작업환경일 때, 장점이 있는 것 같습니다.
즉 dll 을 각자 하나씩 맡아서 개발하는 경우에 모든 dll 및 exe 가 공유하는 클래스가 있는데 그 클래스의 인스턴스가 한 프로세스 수준에서 유일하개 존재해야 한다면, 혹은 유일하게 존재하도록 하는 것이 메모리 휴율성 등에서 더 스마트한 방법이라면 싱글턴이 좋다는 생각입니다.

싱글턴을 쓰지 않는다면 누군가 그 객체를 생성시킬 책임을 맡아야 하고, 그 객체의 포인터를 매개변수로 다른 DLL 들에게 전달해줘야 하는 애로사항이 생깁니다. 이때 전역 객체 대신 싱글턴을 사용하면 누구든지 그 객체를 걱정없이 정적 맴버 변수로 접근할 수 있습니다. 매개변수로 포인터를 획득하거나 전달할 필요가 없습니다. 그리고 객체 생성의 책임도 한 사람에게 정해지지 않고 순서상 먼저 엑세스 하려는 사람이 자동으로 생성할 수 있습니다. 그리고 또한 전역변수를 dll 들이 공유하는 것은 모듈의 독립성을 해치게 됩니다. 싱글턴은 모듈의 독립성을 높이는 방법도 될 수 있겠네요..

단 주의할 점은 객체 생성시의 철저한 동기화 문제 해결입니다. 이 문제는 그렇게 해결하기 어려운 문제는 아니구요.

제 생각을 winner 님과 나눠보고 싶습니다.

감사합니다.

winner의 이미지

너무 부담주지 마세요.

별도의 file 에 들어가는 전역객체도 모든 file 들에서 참조할 수 있습니다. 함수만 그런 것은 아니고요. 똑같이 공용기호내보내기 할 수 있으니까 의문점이 있으면 MSDN 찾아보시고요.
하지만 그렇다고 사용하신 방법이 의미가 없는 것은 아니고요. C++ 에서 Singleton 논란은 일반적으로 전역객체의 적절한 초기화 순서의 문제입니다. 사용하신 방법은 그런 문제를 해결해주겠죠. 제가 Meyers' Singleton 을 쓰는 이유는 그 문제를 해결할 수 있는 것 중 가장 간단하기 때문입니다. 그 문제만을 고려해서 만들어졌으니까요.
사실 제가 Meyers' Singleton 이라고 표기하고 있는데 더이상 Meyers' Singleton 은 Singleton 이 아닙니다. 제가 알기로 More Effective C++ 에서 Singleton 과 객체생성갯수 제한을 함수의 지역정적객체의 참조반환기법을 통해 해결하는 기법을 설명했고, 여러 사람들에 의해서 이것을 Meyers' Singleton 이라고 불렸는데 그후 출판한 Effective C++ 3판에서 이 기법을 설명할 때는 Singleton 은 빼버리고, 전역객체 초기화순서문제 해결법으로 설명하더군요. 물론 Singleton 구현기법의 기반으로 사용해도 문제가 있는 것은 아닙니다.

간단하고 깔끔한 해결책이 항상 답이 되는 것도 아닙니다. Meyers' Singleton은 C++ 표준의 직관적으로 이해되지 않는 특이한 점에 의존하고 있기에 C++ 에 정통하신 분들이 아니면 오해될 수 있습니다. 그렇다고 개발팀 모두에게 그 사항을 이해시켜야 한다면 그게 더 부담이 될 수 있거든요. 앞에서 썼듯 복잡도는 사라지지 않고 제어될 뿐입니다. Meyers' Singleton 은 Singleton 과 전역객체 초기화순서 문제를 C++ 표준에 의존해서 간단하게 구현할 수 있게 해주었죠. 덕분에 C++ 표준과 compiler 구현은 조금 더 복잡해졌고, source를 읽는 사람들은 그 사항을 이해해야 한다는 부담이 생깁니다.

무엇이든 환경에 따라 장단점이 있으니까 적절하게 선택하셔서 사용하시면 됩니다. 제가 Singleton 이야기만 나오면 비판하는 것은 Singleton 이 전역변수의 문제를 해결해주는 것으로 잘못 설명되면서 무분별하게 사용되는 것을 경계하기 위해서입니다.
정리해서 말씀드리면 전역객체는 다음의 두가지 문제가 있습니다.
1. 전역접근이 과도하게 이뤄질 가능성. 그로 인한 변경의 어려움.
2. 복잡한 객체들의 적절한 초기화의 어려움.

Singleton 은 2에 대해서 해결할 수 있을 뿐(사실 Singleton 이라서 해결되는 것은 아니고, 별도의 접근함수가 있으니까 해결되는 것) 1은 아닙니다. 기본적으로 1은 구현기법으로 해결할 수 있는 문제가 아니죠. 구현의 관리(적당한 설계)를 통해서 해결해야 하는 문제입니다.

jeongheumjo의 이미지

제가 알지못하는 부분들에 대해 더 알아야겠다는 자극을 주셔서 감사하구요.
제가 순간적인 생각이 났을 때 성급하게도 이곳에 글을 쓰게 되는 것 같아요.
모듈 프로그램 환경에서의 제 생각이 인정받지는 못했네요.. ^^;

친절한 답변 감사드립니다.

익명 사용자의 이미지

요즘은 보통의 프로그램은 multi-thread이고 cpu는 multi-core일 경우가 대부분일 겁니다.

그 경우 sync를 위해 lock을 사용하는것은 성능을 감소시키는 것 같기도 하고 그래서

요즘은 lock-free queue를 사용한 active object 패턴을 사용하는게 어떨까 합니다.

http://drdobbs.com/article/print?articleId=225700095&siteSectionName=

jeongheumjo의 이미지

'자바로 구현한 디자인패턴'이란 책에 소개가 되었구요,
안드로이드의 오픈코어가 액티브오브젝트 패턴 기반이더라구요..
그런데 왜 그렇게 복잡하게 패턴을 구현했을까 몰랐는데 그게 동기화 부담을 덜기 위한 목적인가요?
액티브 오브젝트 패턴을 참 자주 듣게 되는 것 같습니다.

emptynote의 이미지

[냉무] 헉... ACE 에서 쓰인 패턴이군요.

개인적 푸념 하나 하자면,

완전한 실행파일을 제공하지 않고 의사코드로 수준으로 소개한 패턴은 자기것으로 소화하기가 어렵군요.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.