싱글톤 사용시 할 때

프로그래밍 일반에 관한 포럼입니다.

Moderator: 류광

Locked
비회원

싱글톤 사용시 할 때

Post by 비회원 »

GPG에 나온 싱글톤이나, 이곳에 나온 싱글톤을 보면
싱글톤 클래스의 생성자, 복사생성자와 싱글톤 클래스의 파생 클래스의
생성자와, 복사생성자를 protected로 해야하지 않나요?
그런데 싱글톤 예제를 보면 그렇지 않던데요,
다른분들은 어떻게 사용하시나요?
비회원

음..

Post by 비회원 »

그냥 별 생각 없이 짜다가 public에 들어가기도 하고

protected에 들어가기도 합니다.

외부에서의 생성을 막으려면 protected에 들어가는 것이 맞겠죠.
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

싱글톤의 목적은 '인스턴스가 하나'임을 보장하는데 있습니다.

생성자를 protected로 하게되면 동적으로 싱글턴을 선택하여
어플리케이션을 돌릴수가 없게됩니다.
예를 들자면 그래픽 디바이스 싱글턴이 하드웨어의 종류에 따라
다른 클래스로 인스턴스화 되어야 할 경우
생성자가 protected라면 구현난감입니다.
비회원

Post by 비회원 »

Gamza wrote:싱글톤의 목적은 '인스턴스가 하나'임을 보장하는데 있습니다.

생성자를 protected로 하게되면 동적으로 싱글턴을 선택하여
어플리케이션을 돌릴수가 없게됩니다.
예를 들자면 그래픽 디바이스 싱글턴이 하드웨어의 종류에 따라
다른 클래스로 인스턴스화 되어야 할 경우
생성자가 protected라면 구현난감입니다.
생성자가 protected일때 구현이 난감해 지는 경우가 어떤 경우인지요?
비회원

...

Post by 비회원 »

싱글톤에서 생성자가 protected나 private가 아니라면 어떻게 '하나의 인스턴스를 보장' 할 수 있나요?
명세에서 절대 하지마시오? 라고 써놓는건 아니겠지요?
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

결국 싱글톤 객체가 하나임을 보장하는 것은
싱글톤을 상속받은 객체에서 어떻게 할 수 있는 일이 아닙니다.

물론 Singleton에서 인스턴스가 하나가 됨을 보장하도록 코딩하는것,
그 역시 "사실상" 구현불가 입니다.

(그렇게 하려면 상속,생성순서,생성자구현...에 따르는 몇몇 조건이 붙게됩니다.)

Singleton이 할 수 있는 일이라고는 고작해야 아래 정도입니다.
- 두개의 인스턴스가 생성될 경우 assert failed를 발생.
- (혹여 인스턴스가 두개가 생성되었을 지라도) Singleton인스턴스를 가져다 쓰는 시스템에게
하나의 인스턴스를 돌려줌을 보장하는것.
이게 최선이죠.

-----------------------------------------------------------------------------------------------------------

아래는 난감한 경우의 예입니다.

Code: Select all

class Renderer : public Singleton<Renderer> { ... }
class Software_Renderer : public Renderer { ... }
class DirectX10_Renderer : public Renderer { ... }

시스템초기화()
{
    if( 시스템이 DirectX10을 지원하는가 ?) { new DirectX10_Renderer; } // 난감...
    else { new Software_Renderer; } // 난감....
}
여기에서, new를 사용하지 않고 Software_Renderer나 DirectX10_Renderer클래스에
CreateSingleton같은 함수를 넣으면 되지 않겠는가?
=> 그렇게 한다면 결국 생성자를 public으로 뽑는것과 다를바가 없습니다.

Code: Select all

시스템초기화()
{
    // 싱글톤을 두개 생성하는 코드
    new DirectX10_Renderer;
    new Software_Renderer;
    // 역시, 싱글톤을 두개 생성하는 코드
    DirectX10_Renderer::CreateSingleton();
    Software_Renderer::CreateSingleton();
}
비회원

Post by 비회원 »

아래는 난감한 경우의 예입니다.
코드:
class Renderer : public Singleton<Renderer> { ... }
class Software_Renderer : public Renderer { ... }
class DirectX10_Renderer : public Renderer { ... }

시스템초기화()
{
if( 시스템이 DirectX10을 지원하는가 ?) { new DirectX10_Renderer; } // 난감...
else { new Software_Renderer; } // 난감....
}
위의 예는 팩토리 패턴과 싱글톤 패턴이 혼용이 되어 난감해진 경우가 아닌가 합니다.

Code: Select all

class Renderer{
public:
	virtual void	Render(){}
protected:
	Renderer(){}
};

class DX10_Renderer : public Renderer{
public:
	virtual void	render(){}
protected:
	friend class RendererFactory;
	DX10_Renderer(){}
};
class SW_Renderer : public Renderer{
public:
	virtual void	render(){}
protected:
	friend class RendererFactory;
	SW_Renderer(){}
};

class RendererFactory{
protected:
	friend class testSingleton;
	static Renderer*	CreateRender(){
		if( isSupportDx10() )
			return new DX10_Renderer;
		return new SW_Renderer;
	}

private:
	static int	isSupportDx10(){
		return 1;
	}
	RendererFactory(){}
};


class RenderSingleton
{
public:
	static Renderer* Instance(){
		static Renderer* pRenderer = RendererFactory::CreateRender();
		return pRenderer;
	}
private:
	RenderSingleton(){}
};
개인적으로 이렇게 friend를 쓰는데 찬성하지는 않지만(더 좋은 방법이 있을 거 같지만 당장 생각나지는 않네요) 이런식으로 Renderer 구현부를 감추고 팩토리패턴과 싱글톤 패턴을 분리를 하면 난감해지는 경우가 없어 지지 않을까 합니다.
이 방식에선 Rendere추상 클래스와 싱글톤 부분만 노출이 되고 모든 렌더러 객체의 생성은 Singleton부분에서만 이루어 지게 됩니다. 물론 '하나의 인스턴스가 보장' 되지요.
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

시스템초기화() 에서 하던 고민이 팩토리로 옮겨졌을 뿐 문제는 그대로 남아있는 것입니다.
결국 팩토리에서 싱글톤의 인스턴스를 생성해야 합니다
그래서 friend라는 방법이외의 다른 방법이 떠오르지 않는 것입니다.

제시하신 코드는 싱글톤이나 protected로 된 생성자가 하나의 인스턴스를 보장하는 것이 아닙니다.
팩토리가 두개의 인스턴스를 만드는것을 싱글톤이나 protected생성자가 막지 못하기 때문에
하나의 인스턴스를 보장하는 것은 '팩토리+protected생성자' 입니다.

근본적으로 하나의 인스턴스를 보장하는 싱글톤은 사실상('상속불가'따위의 조건을 붙이지 않는한)
구현불가 입니다.
비회원

Post by 비회원 »

시스템초기화() 에서 하던 고민이 팩토리로 옮겨졌을 뿐 문제는 그대로 남아있는 것입니다.
결국 팩토리에서 싱글톤의 인스턴스를 생성해야 합니다
그래서 friend라는 방법이외의 다른 방법이 떠오르지 않는 것입니다.
어떠한 문제가 그대로 남아 있는지요? 시스템초기화() 부분과는 차이가 좀 있어 보입니다.
시스템초기화() 부분의 내용은 그 객체를 어느 곳의 누구든간에 인스턴스화 시킬수 있게끔 보여집니다.
그러한 메소드가 이미 노출 되어 있다는 것이죠. 그런것을 막자고 하는것이 싱글톤 패턴이 아닌가 합니다.

아시겠지만 다시 한번 이야기 해보면 싱글톤은
클래스의 인스턴스는 오직 하나임을 보장하며 이 인스턴스에 접근할 수 있는 방법을 제공한다.
GoF에 나오는 싱글톤의 소개입니다. 이 패턴의 목표는

1. 접근하는 인스턴스는 오직 하나란 것이 보장이 되는 방법을 제시 해야 하며
2. 이렇게 유일한 인스턴스에 접근 할 수 있는 방법을 제공하여야 한다.

이겠지요.

class someObject{};
란 객체가 존재를 한다면 1번 조건을 만족 시키기 위하여 오직 1곳에서만 인스턴스화 시켜야 하며 2번 조건을 만족 하기 위하여 인스턴스화 시킨곳을 통하여 접근할 수 있어야 합니다.

위의 예처럼 renderer라는 객체가 DX_Renderer도 되어야 하고 SW_Renderer도 되어야 한다면 이 객체의 인스턴스는 2개입니다. 유일하지 않다는 것이죠. 하지만 일반적으로는 DX나 SW둘중하나를 시스템이 시작할 때 결정이 되기 때문에 이 객체는 DX_Renderer도 아니고 SW_Renderer도 아닌 renderer란 유일한 객체하나만 보게 되겠지요.

위의 firend를 사용한 예제는
1. 1번 조건을 만족 시키기 위해서 모든 객체를 protected혹은 private로 감추었습니다. 이제 이 객체를 인스턴스화 시킬 수 있는 방법은 없어 보입니다.
2. 하나란 것을 보장하기 위하여 friend로 factory로 위임을 하고 있습니다.
3. factory도 생성자를 private로 두어 인스턴스화를 막았습니다. 그리고 필요한 메소드는 protected된 Create메소드를 제공하고 이것을 다시 싱글톤객체를 friend로 둡니다.
4. 2번 조건을 만족 하기 위하여 싱글톤은 유일한 인스턴스에 접근 할 수있게 하기윈한 방법으로 Instance란 메소드를 제공 합니다.

이렇게 보면 renderer, sw_renderer, dx_renderer, rendererFactory는 정상적인 방법으로는 어느 곳이든 어떠한 방법으로든 간에 인스턴스화 시킬수 있는 방법이 없습니다. 오직 싱글톤 객체를 제외 하고는 말이죠.

이제 싱글톤 객체에서 1과 2의 방법을 구현 하기만 하면 됩니다. 더더욱 재미 있는 것은 실제 이 코드를 보는 쪽에서는 sw_renderer와 dx_renderer, rendereFactory는 구현부에 감춰 지기 때문에 완벽하게 은닉 시킬 수 있다는겁니다. 클라이언트는 renderer의 인스턴스만 보는것이죠.

이게 싱글톤이 아니라면 무엇일까요? 여기에 어떤 허점이 있나요?
gustwind
Posts: 89
Joined: 2005-10-04 18:05
Location: echinoidimes

Post by gustwind »

이게 싱글톤이 아니라면 무엇일까요? 여기에 어떤 허점이 있나요?
Renderer의 해제는 어디서 하나요?
nikola2
Posts: 886
Joined: 2005-07-12 01:19
Location: (주) 신규 소프트

Post by nikola2 »

gustwind wrote:
이게 싱글톤이 아니라면 무엇일까요? 여기에 어떤 허점이 있나요?
Renderer의 해제는 어디서 하나요?
허점이라고 보기는 어려울 것 같은데요.....누락된것이라고 봐야될것같은데...

명시적으로 static Destroy()함수를 호출하던가...
아니면....
팩토리클래스의....new로 생성한 객체를 생성했던 공간에서....
atexit()에 Destroy함수를 등록해버리고, 프로그램 종료시에 지우게 하는 방법이 있었던 것 같습니다..
물론 Destroy()만큼은 public이 될테구요....
(Modern C++ Design)
집사람이 국력이다...
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

1.
'생성자가 protected인데 인스턴스를 어떻게 생성할 수 있는가?'
라는 문제가 그대로 남아있습니다.
(아시다 시피 friend는 해결책이 아닙니다.)

2.
제시하신 코드에서
'하나의 인스턴스를 보장하는 것은 '팩토리+protected생성자' 입니다.
싱글톤이 보장하는 것이 아닙니다.
어떠한 임의의 클래스가
'생성자가 protected' 이고 '팩토리가 하나의 인스턴스만 생성' 한다면
인스턴스는 하나일 수밖에 없습니다.
제시하신 코드에서 인스턴스가 하나인 것은 싱글톤 클래스와는 전혀 무관합니다.
비회원

^^

Post by 비회원 »

싱글톤이 보장하는 것이 아닙니다.
어떠한 임의의 클래스가
'생성자가 protected' 이고 '팩토리가 하나의 인스턴스만 생성' 한다면
인스턴스는 하나일 수밖에 없습니다.
이게 바로 싱글톤이잖아요. 유일한 인스턴스를 보장하고 있고 그 인스턴스를 접근 가능하게되어 있지 않나요?
위의 예제에서 factory는 싱글톤과 팩토리패턴을 분리하기 위해서 만든 객체이고 factory 자체가 싱글톤클래스가 될수도 있겠지요.

Code: Select all


// header
class IRenderer
{
public:
	virtual void render() = 0;
	IRenderer(){}
};

class RendererSingleton
{
public:
	static IRenderer* instance();	
private:
	class RenderImpl;
	static RenderImpl*		m_pImpl;	
	RendererSingleton();
};

void doSomeRender(){
	IRenderer* p = RendererSingleton::instance();
	p->render();
}

// cpp
class RendererSingleton::RenderImpl
{
public:
	IRenderer*			GetRenderer(){
                                SAFE_DELETE( m_pRenderer );
		switch( getRendererSupportMode() ){
		case 0:
			m_pRenderer = new SW_render;
			break;
		case 1:
			m_pRenderer = new DX10_render;
			break;
		}

		return m_pRenderer;
	}
	class SW_render : public IRenderer{
	public:
		SW_render(){}
		virtual void render(){ std::cout << "sw_render" << std::endl; }
	};
	class DX10_render : public IRenderer{
	public:
		DX10_render(){}
		virtual void render(){ std::cout << "dx10_render" <<std>GetRenderer();	
	return pInstance;
}

와 같은 형식은 어떤지요. pImpl 패턴입니다. 이 구현 자체로 보면 Renderer객체들이 종속되어있는 느낌이 좀 들지만 template을 사용하면 완전 분리도 할수 있을겁니다.
어쨋든 Rederer객체 자체를 싱글톤에 종속시켜버렸습니다. 그리고 public 생성자도 가지고 있구요.
싱글톤 자체를 부정을 하시니 저도 약간 난감 하네요. 절대 구현이 불가 한 모호한 패턴이 널리 사용되고 있다고는 믿어지지가 않습니다.

ps. 객체의 소멸은 위에서 말씀 하신대로 atexit()에 파괴자 메소드를 등록하거나 아니면 명시적으로 호출하게끔 하면 되겠지요.
비회원

Post by 비회원 »

Code: Select all

RendererSingleton::RenderImpl* RendererSingleton::m_pImpl = new RendererSingleton::RenderImpl;

IRenderer* RendererSingleton::instance(){
	static IRenderer* pInstance = m_pImpl->GetRenderer();	
	return pInstance;
}
위에서 이 코드가 빠져 있네요.
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

생각없이 용어를 쓰다보니 의미가 잘못 전달 되었나 봅니다.
제 글에서 언급된 '싱글톤'은 'GPG에 나온 자동적인 단일체 유틸리티'를 의미합니다.
(처음질문의 영향으로 저도 모르게 그만 머리속에서 뒤섞여 버렸나 봅니다)

제가 언급하려 했던것은
제시하신 코드는 싱글톤의 구현을
'GPG에 나온 자동적인 단일체 유틸리티'로 하고있는것이 아니고
'팩토리+friend+private생성자'로 한 것입니다.

가 되겠네요.
Last edited by Gamza on 2006-11-28 09:02, edited 2 times in total.
비회원

싱글톤의 사용목적은 단일체 아닌가요?

Post by 비회원 »

생성자가 public으로 되어있다면 클라이언트 입장에서 객채 생성이 컴파일시 참이 됩니다.
실생시 오류를 뱃을수 있또록 짤수도 있겠지만 좋은 방버은 아닌것 같습니다.

스트레티지 패턴을 사용해서 바뀔수 있는 부분을 모듈화해서 싱글톤 내부에서 알아서 사용할 모듈을
생성하는 방법을 사용하는게 좋을 것 같습니다.
nikola2
Posts: 886
Joined: 2005-07-12 01:19
Location: (주) 신규 소프트

Re: 싱글톤의 사용목적은 단일체 아닌가요?

Post by nikola2 »

비회원 wrote:생성자가 public으로 되어있다면 클라이언트 입장에서 객채 생성이 컴파일시 참이 됩니다.
실생시 오류를 뱃을수 있또록 짤수도 있겠지만 좋은 방버은 아닌것 같습니다.

스트레티지 패턴을 사용해서 바뀔수 있는 부분을 모듈화해서 싱글톤 내부에서 알아서 사용할 모듈을
생성하는 방법을 사용하는게 좋을 것 같습니다.
제 생각엔 스트레티지 패턴을 쓴다고 하면, 싱글턴 내부에서만 쓰여야하고, 그렇게 되면
싱글턴은 자동화된 모듈이라기보다는 응용클래스의 구현 패턴이 됩니다.....

gpg1책에서의 예도 그렇고, 적지않은 단일체 유틸리티가 public 생성을 허용하는데...
여기서는 자동화된 싱글턴객체라면.......그냥 싱글턴이 아니라,
'자동화'언급이 붙으므로 템플릿/상속을 써서 모듈화스타일로 가게됩니다......
이때, friend를 사용하던가, public 생성자를 허용하던가 둘중 하나의 양자택일의 경우에 처하는 경우가
있습니다.....

그래서...싱글톤 코드를 여기에 적어주신 비회원님의 많은 글들을 잘보았습니다만......
만약에 탬플릿을 통해서 생성 팩토리를 외부에 빼게 되면 그 즉시 friend 아니면 public 생성자 둘중
하나를 선택해야 될듯합니다.....friend를 선언하면 결합도가 너무 높아지는 셈이고, public 생성자를
허용하면 싱글턴의 원래의 정의에서 벗어나게 되는데요...
이것을 해결해줄 방법이 아마도 없을것 같다는게 제 개인적인 생각입니다...
집사람이 국력이다...
Locked