클래스 생성시 ...

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

Moderator: 류광

Locked
비회원

클래스 생성시 ...

Post by 비회원 »

3D 프로그램을 작성하면서, 지형이나 셰이더 부분, 모델 오브젝트 등등을 각각 클래스 단위로 만들고 있는데요.

예전에 한 프로젝트에서도 그랬었고, 최근까지는 다음과 같이 작성을 했었거든요. 예를 들어 지형 객체라면 ... 다음과 같이 코딩 했었습니다.

Code: Select all

    ....
    m_pTerrain = new CTerrain;
    m_pTerrain->LoadRawFile("513x513.raw",513, 100, 30);
    m_pTerrain->LoadBaseTexture("base.jpg");
    m_pTerrain->LoadDetailTexture("Detail.jpg");
    ....
이렇게 쓰다보니까, 문제가 new 에서 생성 단계를 거쳐 실수로 Load를 빼먹거나 해도, - 실제로 내용이 생성되는 부분은 LoadRawFile메서드 - 실제로 NULL 포인터가 아니니까 생성이 되었는지 아닌지를 판단하기가 애매하더군요. 코드를 읽어도 실제로 생성되는 부분이 new인지 아닌지도 애매하죠.

그래서, 생성자에 바로 맵의 생성부분을 써 넣을까 하고 생각해봤었습니다. 그러다가, 우연히 Python 가지고 놀다가 C++에서도 다음과 같이 하면 훨씬 더 깔끔하지 않을까 하는 생각이 들더군요.

Code: Select all

    m_pTerrain = CTerrain::CreateHeightMap("513x513.raw", 100, 30);
    ....
오버로딩의 편리함은 없을지 모르지만, 훨씬 명시적이니까 더 좋을 것 같은데 어떻게 생각하시는지요 ? - 물론 CreateHeightMap 메서드는 static 이고, 내부적으로는 new를 이용해 생성된 객체를 리턴하도록 만들어야죠.
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

훨씬 더 명시적인 것 같지는 않습니다. 왜냐하면 CreateHeightMap은 이름은 높이 맵을 만드는 함수인데 지형을 돌려주니까요.. :)

위의 예에서 하나의 지형은 높이 맵, 기본 텍스처, 세부 텍스처로 만들어지니 이런 형태의 생성자가 기본이지 않을까요?

Code: Select all

    m_pTerrain = new CTerrain( pHeightMap, 
        pBaseTexture, pDetailTexture);
이런 차원에서 본다면 LoadXXX 메서드들이 CTerrain의 public 메서드인 것은 좀 이상합니다. 오히려 파일 관리 유틸리티의 정적 메서드들로 두는 게 더 자연스러운 것 같습니다. 또는 해당 기능성을 CTerrain 안으로 숨기고 이런 형태의 생성자를 줄 수도 있구요.

Code: Select all

    m_pTerrain = new CTerrain( "513x513.raw", 513, 100, 30,
        "base.jpg", "Detail.jpg");
여기까지 왔다면 위에서 제시한 것처럼 갈 수 있겠죠...

Code: Select all

    m_pTerrain = CTerrain::CreateInstance( "513x513.raw", 
        513, 100, 30,
        "base.jpg", "Detail.jpg");
그러나 위의 생성자보다 크게 나은 것 같지는 않습니다...
Testors
Posts: 557
Joined: 2003-07-26 00:34
Location: (주)nFlavor
Contact:

Post by Testors »

류광 wrote:

Code: Select all

    m_pTerrain = new CTerrain( "513x513.raw", 513, 100, 30,
        "base.jpg", "Detail.jpg");
여기까지 왔다면 위에서 제시한 것처럼 갈 수 있겠죠...

Code: Select all

    m_pTerrain = CTerrain::CreateInstance( "513x513.raw", 
        513, 100, 30,
        "base.jpg", "Detail.jpg");
그러나 위의 생성자보다 크게 나은 것 같지는 않습니다...
생성자로 만들게 되면 에러 핸들링 수단이 예외처리로 제한되는 단점이 있겠네요.

후자는 NULL 체크, 예외 둘다 가능.
플머/모델러/애니메이터 구해염 **현역/보충역 병특가능** / http://testors.net/
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

전자의 경우에도 꼭 예외를 사용할 필요 없이 예를 들어 if(m_pTerrain->isConstrunctionCompleted) 같은 형태가 가능합니다. 또 필요하다면 isBaseTextureLoaded 처럼 개별 작업의 성패를 점검할 수도 있겠구요.. 이 부분은 정적 생성 메서드도 마찬가지일 것입니다. 즉 오류 처리 수단의 차이는 없다고 봐도 될 것입니다....
Testors
Posts: 557
Joined: 2003-07-26 00:34
Location: (주)nFlavor
Contact:

Post by Testors »

류광 wrote:전자의 경우에도 꼭 예외를 사용할 필요 없이 예를 들어 if(m_pTerrain->isConstrunctionCompleted) 같은 형태가 가능합니다. 또 필요하다면 isBaseTextureLoaded 처럼 개별 작업의 성패를 점검할 수도 있겠구요.. 이 부분은 정적 생성 메서드도 마찬가지일 것입니다. 즉 오류 처리 수단의 차이는 없다고 봐도 될 것입니다....
원글쓰신 분의 의도는 "인스턴스는 존재하지만 완전하지 않아 사용할 수 없는 상태" 를 없애는, 실체에 대한 무결성 보장에 있다고 생각 됩니다. 그래서 생성자 이후에 Load 부분이 안되었다던가 하는 얘기를 언급하신듯 싶구요, 결국 초기화가 제대로 되어 인스턴스가 리턴되던지 혹은 아예 인스턴스가 생성되지 않는 방법을 제시하신것이겠죠?

류광님이 제시하신 객체 생성 후에 다시 isValid 류의 검사를 하는 방법은 '완전하지 않은 실체' 의 존재를 인정하는 것이겠지요. 하지만 본래 의도에는 벗어난듯 합니다. 결국 인스턴스의 무결성 문제를 해결하기 위한 방법은 위의 두 방법으로 좁혀질것 같고 그렇다면 생성자를 통한 방법은 여전히 에러 핸들링 수단이 예외처리로 한정되는 단점이 있을것 같습니다만.. :wink:
플머/모델러/애니메이터 구해염 **현역/보충역 병특가능** / http://testors.net/
pacman
Posts: 188
Joined: 2004-04-13 12:48

생성자는 가볍게!

Post by pacman »

윈도우 클래스 생성할 때(WNDCLASSEX)나 DirectX 3D 생성할 때(D3DPRESENT_PARAMETERS) 처럼
필요한 인자를 구조체로 묶어서 넘기는 방법도 있습니다.
묶어서 넘기니까 인자가 많아도 상관없죠.
클래스 생성시 필요한 인자가 많은 경우 유용한 방법입니다.
그리고, 가급적이면 생성자는 멤버 변수 초기화 정도로 "가볍게" 구현하시고,
생성 함수(Make 또는 Create)를 "따로" 만들어 두는 것이 좋습니다. 에러 식별에도 용이하구요.
예를 들면,

Code: Select all

// ★렌즈 플레어 생성 인자
struct xLensFlareParameters
{
	int				iTexID;			// 텍스처 ID
	int				iNumFlares;			// 플레어 개수 (1~10)
	float				fScale;			// 화면 대비 크기 비율 (0.0f~1.0f)
	float				fMaxScale;			// 화면 대비 최대 크기 비율 (0.0f~1.0f)
	float				fMaxAlpha;			// 최대 불투명도 (0.0f~1.0f)
	xVector			vMinColor;			// 색상
	xVector			vMaxColor;
};

// ★렌즈 플레어
class xLensFlare
{
public:
	xLensFlare();
	~xLensFlare();

	void	Empty();
	bool	Make(const xLensFlareParameters* p);
	void	Draw(xVector& vLightOrigin);
	
private:
	// 생략
};

xLensFlare* pFlare = NULL;
bool Init()
{
	// ...
	// 렌즈 플레어
	xLensFlareParameters fp;
	fp.iTexID = xMakeTexture("LensFlare512.tga");	// 아래 류광님이 말씀하신 무결성 검사 생략 -.-;
	fp.iNumFlares = 10;
	fp.fScale = 0.5f;
	fp.fMaxScale = 0.6f;
	fp.fMaxAlpha = 0.4f;
	fp.vMinColor = xVector(0.50f, 0.50f, 0.50f);
	fp.vMaxColor = xVector(1.0f, 1.0f, 1.0f);

	pFlare = new xLensFlare;
	ASSERT(pFlare);

	if(!pFlare->Make(&fp)) return false;
	return true;
}
이런 식으로요... :D
Last edited by pacman on 2004-07-15 17:40, edited 3 times in total.
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

Testors wrote: 원글쓰신 분의 의도는 "인스턴스는 존재하지만 완전하지 않아 사용할 수 없는 상태" 를 없애는, 실체에 대한 무결성 보장에 있다고 생각 됩니다. 그래서 생성자 이후에 Load 부분이 안되었다던가 하는 얘기를 언급하신듯 싶구요, 결국 초기화가 제대로 되어 인스턴스가 리턴되던지 혹은 아예 인스턴스가 생성되지 않는 방법을 제시하신것이겠죠?

류광님이 제시하신 객체 생성 후에 다시 isValid 류의 검사를 하는 방법은 '완전하지 않은 실체' 의 존재를 인정하는 것이겠지요. 하지만 본래 의도에는 벗어난듯 합니다. 결국 인스턴스의 무결성 문제를 해결하기 위한 방법은 위의 두 방법으로 좁혀질것 같고 그렇다면 생성자를 통한 방법은 여전히 에러 핸들링 수단이 예외처리로 한정되는 단점이 있을것 같습니다만..
예 제가 애초에 이야기하고 싶은 것도 바로 그 점이었습니다. 원 글 쓰신 분은 건전한 문제 의식으로 출발하긴 했지만 의외로 CreateObject나 CreateInstance가 아니라 CreateHeightMap를 제시했구요. 이는 그 다음에 LoadBaseTexture, LoadDetailTexture가 따를 것을 암시한다고 봤습니다. 즉 부분적으로 생성된 객체가 여러 함수 호출을 거쳐서 완성된다는 개념은 여전히 남아 있던 것이죠...

그래서 제가 처음에 제시한 것은 어떤 형태이든 그 과정을 하나의 호출로 통합할 필요가 있다는 것이었구요...

다시 Testor 님 이야기로 돌아와서, 이 경우(즉 생성 호출에 "513x513.raw", "base.jpg", "Detail.jpg" 같은 인자들이 존재하는 상황)이라면 상황 자체가 좀 지저분하지 않나 싶네요. 이런 상황에서는 단지 NULL을 통해 전부/전무를 검사하는 걸로는 부족할 것입니다. 어떤 형태로든 뭐가 문제인지를 알 수 있어야 할 것입니다. 적어도 사용자에게 표시할 대화상자를 위해서라도요....

물론 좀 더 나은 해결책은 애초에 "513x513.raw", "base.jpg", "Detail.jpg" 같은 것들이 개별적인 객체 형태로(즉 이미 무결성이 검증된 존재로) CTerrain의 생성에 도입되게 하는 것일 겁니다.
조순현
Posts: 115
Joined: 2004-07-02 22:14
Location: 네오위즈 게임즈
Contact:

예외 처리

Post by 조순현 »

일단, 따로 객체를 생성해서 인자로 넘기느냐, 아니면 내부적으로
생성하느냐는 큰 차이는 없어보입니다. code의 추상화 측면을
보자면 내부에서 생성하는 방식이 더 좋은 것 같긴 합니다;

그건 제쳐두고, 저는 그 객체를 받아 terrain을 생성하는 부분에
초점을 맞춰서 생각해보겠습니다. 윗분들이 몇가지 방법을 제시하셨는데요,
모두 나름대로 장단점이 있습니다만...

일단 isValid... 식의 검사는 좋지 않다고 봅니다. 왜냐면
그 함수를 호출해야 한다는 것은 <resource 획득은 초기화>라는
좋은 개념에 어긋납니다. 객체는 생성된 순간, 사용 가능해야 합니다.
또 검사를 빼먹는 것은 쉬운 일이고,
그렇다면 bug의 가능성은 언제나 도사리고 있습니다. 그리고
만약 그 객체를 사용하는 사람이 검사를 빼먹을 경우를 대비해서,
그 객체 내부의 함수에서 현재 객체의 상태가 특정 조건을 만족하는지
검사를 해주는 방법도 있습니다. 하지만 code가 지저분해질 뿐만
아니라 근본적인 문제를 단지 객체 외부에서 내부로 옮겼을 뿐입니다.

NULL 검사는 예외 처리에 드는 부하도 없고, 실제로 많이
사용되는 방법입니다. 하지만 이것 역시 검사를 빼먹을 수 있다는
점에서 잠재적인 위험이 있습니다. 물론 검사를 빼먹어도 NULL pointer를
역참조할 때 오류가 발생하므로 어떻게든 문제점은 발견되지만, 그 원인의
정확한 위치를 파악하기 힘들다는 문제가 있습니다. 또 깊은
함수 호출이 일어날 경우, 함수들이 일일이 성공/실패 여부를 되돌려야
한다는 문제도 있습니다.

예외 처리는 run-time 강제성이 있다는 점에서 좋습니다.
단점은 예외가 발생했을 때 완벽히 복구(예외를 처리한 후 계속
실행가능하게 함)되게 하는 것은 어렵다는 것과, 복구 시에 부하가
크다는 것입니다. 예외 발생시 정확한 복구가 어렵다는 것은
Exceptional C++, More Exceptional C++ 책을 보면 아실 것입니다.
예외 복구에 안전한 code를 만드는 건 배보다 배꼽이 더 커집니다;

비록 예외 복구가 어렵다해도 결론은...실행시에 부하가 많이 걸릴
정도로 잦은 복구가 필요한 곳에는 NULL 비교와 같은 방식을 쓰고,
그 외에는 최대한 예외 처리를 이용하는 게 좋은 것 같습니다.
하지만...아직도 C만을 고집하는 사람이 많듯이 자기 편한대로
쓰는 것이 좋을지도... :roll:
Last edited by 조순현 on 2004-07-16 10:50, edited 2 times in total.
쉬운 것은 올바르다.
jungmoona
Posts: 111
Joined: 2002-03-12 09:00

제가 하는방식은 이렇습니다.

Post by jungmoona »

논제를 제기하신분의 요지는

Terrain 클래스 같은 게임 내에서 전역적으로 쓰이는 객체를 생성하고서
사용하려면 필수적으로 호출해야할 Load 메소드가 있는데
이중 호출되지 않은 것이 있다면,또는 Load메소드중 실패한것이 있다면
게임내에서 Terrain 클래스는 사용을 못하게 하고싶다.

인것으로 저는 파악했습니다.

전 이런상황을 일단 이런식으로 처리하곤 합니다.

Code: Select all

CTerrain* pTmp = new CTerrain;
try{
pTmp->Loadxxx1();
pTmp->Loadxxx2();
pTmp->Loadxxx3();

_ObjectList.push_back(pTmp);

}catch(exception e)
{
   SAFE_DELETE(pTmp);
    Log( e.what() + "의 이유로 Cterrain 추가가 아니되오" );
}

물론 Load메소드는 예외발생가능하며
Load메소드가 하나라도 실패하면 애초에 리스트에 삽입을 안시키는거죠
Load메소드가 실패했다면 단지 화면에 지형이 안나오는 정도이므로
게임이 아무런 정보도 없이 튕기는 것보단 그나마 나을겁니다.

일단
Cobject <- CTerrain 상속관계고
list<CObject*> 를 메인루프에서 돌려주는 것입니다.
물론 Cobject 에는 Render비스무리한 메소드가 추상메소드로 들어있겠죠.

어차피 Load 가 되면 Release 도 사용이 끝난 시점에선
해줘야 하기에 리스트에 모두 넣어서 관리 하곤합니다.
근데 제글이 논제 제기하신분께 도움이 될런지는 모르겠네요
엄한소리 한건 아닌지 @,.@;

ps. 수렁이형 이제 gpg에서 자주 출현하시는군뇨 흫흫
...Another one bites the Dust~!...
조순현
Posts: 115
Joined: 2004-07-02 22:14
Location: 네오위즈 게임즈
Contact:

추가...

Post by 조순현 »

Code: Select all

CTerrain* pTmp = new CTerrain;
try
{
	pTmp->Loadxxx1();
	pTmp->Loadxxx2();
	pTmp->Loadxxx3();

	_ObjectList.push_back(pTmp);
}
catch(exception e)
{
	SAFE_DELETE(pTmp);
	Log( e.what() + "의 이유로 Cterrain 추가가 아니되오" );
}
jungmoona님이 제시한 위의 방식은 특정 Loadxxx 함수를 호출할 때에
어디서 문제가 발생했는지 알 수 있게 해결은 했습니다. 하지만 또다른 문제인
<필요한 모든 Loadxxx 함수를 호출했는가>하는 문제는 해결하지 않은
것입니다. 만약 필요한 모든 Loadxxx 함수 중에서 하나라도
빼먹게 되면 찾을 방법이 없습니다.

<그 정도는 programmer가 당연히 신경써서 확인해야 되는
것 아닌가>라고 생각할 수도 있습니다. 하지만, 인간(위에서는 특히
CTerrain을 사용하는 programmer)은 좀더 창조적 능력이 필요한 곳에
신경을 써야 하고, computer가 더 잘 할 수 있는 것은 computer가
알아서 하게 하는 것이 좋다고 생각합니다.

사족으로 code로 정리하자면...

Code: Select all

try
{
	CTerrain* pTmp = new CTerrain (
		"raw파일" , "base파일" , "detail파일" ) ;
}
catch ( const exception& e )
{
	// 예외 처리
}
위처럼 하고 CTerrain 생성자 내부에서 예외를 발생시키는 방식이
좋다고 생각됩니다. 사용자는 CTerrain이 반드시 필요로 하는
적당한 file 이름을 넘기기만 하면 됩니다. 그것은 강제적이므로
실수할 여지가 줄어들죠. 또 내부적으로 사용되는 raw, base, detail
등의 생성 처리에 대해서는 전혀 신경쓸 필요가 없으니 좀더 추상화가
잘된 거 같습니다.

추신: 하이 jungmoona
쉬운 것은 올바르다.
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

이 주제의 논의 요점이 NULL 점검이 좋으냐 예외가 좋으냐로 넘어가는 것은 반대입니다. 클래스 설계 시 클라이언트에게 최대한 선택권을 주느냐 아니면 특정 방식을 강제하느냐의 문제가 오히려 더 중요할 것 같습니다. 반환값 점검이냐 예외냐는 다른 주제로 이야기했으면 좋겠습니다.

수렁님이 RAII 언급하셨는데 isValid의 존재가 RAII를 헤친다고 보지는 않습니다. RAII는 자동 변수 맥락에서 쓰이는 개념이고 이 경우는 차라리 널 객체 쪽으로 생각을 돌리는 것도 좋을 것 같습니다.

그리고
수렁 wrote: 일단, 따로 객체를 생성해서 인자로 넘기느냐, 아니면 내부적으로
생성하느냐는 큰 차이는 없어보입니다. code의 추상화 측면을
보자면 내부에서 생성하는 방식이 더 좋은 것 같긴 합니다;
이 부분도 좀 더 이야기해 봤으면 좋겠습니다. 만일 자원관리자를 도입한다면 이야기가 또 달라질 수 있을 테니까요...
조순현
Posts: 115
Joined: 2004-07-02 22:14
Location: 네오위즈 게임즈
Contact:

Post by 조순현 »

음... 운영자님의 압박으로 인해 논의 방향을 돌리겠습니다:roll:
전 큰 project의 framework를 설계한 경험이 없으므로
단지 그동안의 작은 경험에 대한 생각을 말하는 것일 뿐입니다.
다른 시각은 언제나 환영합니다.

음...논의가 작은 부분에 대해 필요 이상으로 이루어지는 게
아닌지 생각도 듭니다만;; 어쨌든 주절주절 적어봅니다~


1. class 설계시 client에게 선택권을 주는 것

선택권이 무엇에 대한 선택권인지 잘 이해가 안됩니다만;;;
<new냐, 별도의 생성 함수냐>라면, 저는 new를 쓰겠습니다.
만약 단일체나 factory가 적용돼야 하는 특별한 상황이
아니라면 일관된 방식으로 new를 통해 생성하는 것이 좋은 것
같습니다.


2. resource 획득은 초기화

자동변수뿐만 아니라 자원 획득은 거의 모든 경우에
있어서 초기화(준비 완료)여야 한다고 생각합니다. 생성자의
목적은 객체의 생성과 초기화가 분리돼 있던 것을 언어
차원에서 강제로 통합함으로써 분리로 인한 실수를 막고자
하는 것이라고 생각합니다. 만약 그런 것을 일일이 사용자가
처리한다면 생성자가 존재할 이유가 없겠지요.

그리고...Null 객체는 해당 객체의 존재 유무가 선택 사항일
경우에만 유용하다고 생각하는데, 위의 분이 말씀하신 경우에는
등장하는 어떤 자원도 선택 사항인 것 같지 않습니다. 위의
경우에는 적용할 수 없다고 생각합니다...


3. 생성자의 인수를 객체로 할 것인가, 사용자에게
편리한 type만을 받아들이고 내부적으로 생성할 것인가


생각을 해보니 객체나 객체의 ID(자원 관리자 적용시)등을 넘기는
방식이 좋은 것 같습니다. 한가지 생성자만으로도 받아들이는
객체의 생성자를 다양하게 적용할 수 있으니까요.

추상화 측면에서 보자면 내부적으로 필요한 객체의 존재
유무 존재 자체도 신경쓰지 않을 수 있게 내부적으로 생성할
객체에 대한 모든 정보를 넘기는 것이 좋은 것 같습니다.
하지만 실제로 생성자를 만들다 보면 다양한 종류의 생성
선택 사항이 필요한 경우가 많고, 그러면 객체를 따로 생성하고
넘기는 방식이 받아들이는 객체 쪽에서 추가적인 생성자를
덜 만들어도 되니 좋은 것 같습니다. 즉,

Code: Select all

Terrain::Terrain ( const string& raw_file_name ,
	int raw_option_1, int raw_option_2 , int raw_option_3 ,
	const string& base_texture_file_name ,
	const string& detail_texture_file_name )
위 경우보다

Code: Select all

Raw::Raw ( const string& file_name ,
	int option_1 = 기본값, int option_2 = 기본값, int option_3 = 기본값 ) ;
Terrain::Terrain ( Raw* raw , Texture* base_texture , Texture* detail_texture ) ;
아래의 경우가 Raw나 Texture의 생성자를 Terrain과 관계없이 여러가지로
확장할 수 있으므로 더 나은 것 같습니다. 에...물론 Raw가
Terrain 내부에서만 쓰인다면 Raw를 Terrain의 생성자 매개변수로
받아들이면 안되겠지요.
쉬운 것은 올바르다.
비회원

Post by 비회원 »

수렁 wrote: 1. class 설계시 client에게 선택권을 주는 것

선택권이 무엇에 대한 선택권인지 잘 이해가 안됩니다만;;;
<new냐, 별도의 생성 함수냐>라면, 저는 new를 쓰겠습니다.
만약 단일체나 factory가 적용돼야 하는 특별한 상황이
아니라면 일관된 방식으로 new를 통해 생성하는 것이 좋은 것
같습니다.
저도 선택권이 많은 것에는 찬성입니다.
하지만 new를 이용하는 것이 어떠한 면에서 선택권이 있는지는 이해가 가지 않습니다.

다른면에서 보면, 팩토리나 풀링을 쓰는것이 아니라면 new보다는 AllocObject(),
DeallocObject() 같은 wrapper함수를 만드는것이 나중에 메모리 누수등을
체크하기에 편하기 때문에, 저는 이 방법을 추천합니다.

ps. 물론 new, delete 를 오버로딩하는 방법이 있지만, 잘 모르기때문에 ^^
ps. 누수로 고생을 많이해서..-_-
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

그 문맥에서 선택권이라는 것은 예외냐 반환값이냐 NULL 점검이냐를 말하는 것이었습니다. 즉 오류 처리에서 반환값 점검이 좋으냐 예외가 좋으냐를 이야기하기 전에, 예외만으로 한정하는 게 옳은가의 문제를 제기한 것입니다. 그 부분은 논의의 흐름을 돌리기 위한 것이었으므로 지금은 큰 의미가 없습니다.

널 객체의 언급은, 만일 new가 NULL을 돌려주는 쪽으로 가닥을 잡는다면 의미가 생깁니다. 그리고 각 자원(높이맵, 텍스처 등)을 객체로 캡슐화하고 그것들을 생성자에 넣는 식으로 갈 때에도 의미가 생길 것인데, 제대로 이야기하자면 더 많은 가정들(자원의 지연 로딩, LOD 등)이 도입되어야 하기 때문에 일단 다음 기회로 미루겠습니다.

자원 획득과 초기화에 대한 수렁님의 이야기는 공감이 가지만 C++ 공동체에서 그런 것을 RAII라고 하지는 않습니다. RAII라는 용어를 만든 Stroustrup도 그런 의미로 이야기하지는 않았구요. 공교롭게도 최근 han.comp.lang.c++에서 그 부분과 똑같은 이야기가 있었습니다. http://tinyurl.com/4ogc8 참고.

지금 논의하는 상황은 앞에서 말했듯이 좀 지저분합니다. 지저분한 이유는 객체의 생성에 세 가지 작업이 걸려 있고 세 가지 모두 메모리와 파일 I/O를 다루는 실패하기 쉬운 작업이라는 점입니다.

이런 지저분한(또는 복잡한) 상황에서는 설계 상의 결정 사항들이 여러 갈래로 갈라질 수밖에 없습니다. 자원 관리자 등을 통해서 이미 준비된 자원 객체들을 Terrain의 생성자에 넣는 것이 현실적으로 가장 적합해 보이나 그러면 하나의 객체를 하나의 원자적인 연산으로 완성시킨다는 애초의 원칙과는 멀어집니다. 즉, 어찌 보면 최초에 글에 나온 new, Load, Load의 순서만 뒤집어서 Load, Load, new가 된 것일 뿐일 수도 있는거죠. 어쩌면 애초에 그런 원칙을 적용하고자 했던 게 잘못일 수도 있습니다.

지금까지 우리는 이 상황에 대해 적용할 수 있는 여러 가지 접근방식들을 나열했다고 생각합니다. 그것들 중 어떤 것이 좋은가를 논의하기 위해서는 최초에 질문하신 분이 좀 더 구체적인 상황을 이야기하거나, 또는 각자 자신이 수행하는 또는 겪은 프로젝트의 예를 통해서, 즉 문맥과 함께 이야기하는 게 좋을 것 같습니다.

아 한 가지 빠진 것이 있다면, 생성자의 오류 처리는 예외로 한정된다는 이야기와 관련해서, 생성자 안에서 객체 생성을 무효로 돌리고 결과적으로 new가 NULL을 돌려주게 하는 게 가능하다는 걸 어디서 읽은 적이 있습니다... 확실하지는 않습니다.
비회원

new에서 널 리턴하는 거는..

Post by 비회원 »

operator오버로딩하면 되지 않나요?
Testors
Posts: 557
Joined: 2003-07-26 00:34
Location: (주)nFlavor
Contact:

Post by Testors »

류광 wrote:아 한 가지 빠진 것이 있다면, 생성자의 오류 처리는 예외로 한정된다는 이야기와 관련해서, 생성자 안에서 객체 생성을 무효로 돌리고 결과적으로 new가 NULL을 돌려주게 하는 게 가능하다는 걸 어디서 읽은 적이 있습니다... 확실하지는 않습니다.
비회원 wrote:operator오버로딩하면 되지 않나요?
생성자의 오류처리가 예외로 한정되는것을 피하기 위해 new 가 NULL 을 돌려주는 테크닉을 사용하는것은 100점짜리 해결책은 아닌듯 싶습니다. new 를 통해 생성자로 생성이 가능하다는것은 생성자가 public 멤버란 얘기인데 이것은 인스턴스가 힙에만 생성 가능한것이 아니라 자동변수 혹은 전역변수의 형태로 힙 외에도 생성이 가능하다는 뜻이기 때문입니다. 이런경우 new 오퍼레이터는 아예 호출되지도 않겠지요. 만약 이 테크닉을 구현한 결과 오류처리를 new 오퍼레이터에서만 하게 된다면 객체를 전역에 둘경우 컴파일은 잘 되고 인스턴스가 생기긴 했지만 실제로는 invalid 한.. 모호한 경우가 생겨날 수 있겠지요.. 반면 생성자를 private 로 두고 XXX::CreateInstance() 로 객체를 생성하는 방법은 인스턴스의 생성위치를 힙으로 제한하고 인스턴스의 무결성을 보장할 수 있습니다.
플머/모델러/애니메이터 구해염 **현역/보충역 병특가능** / http://testors.net/
조순현
Posts: 115
Joined: 2004-07-02 22:14
Location: 네오위즈 게임즈
Contact:

Post by 조순현 »

음. 일단 류광님이 지적하신대로 <resource 획득은 초기화>라는
용어는 통용되는 개념과 다른 개념으로 제가 사용했네요;
받아들이는 입장에서 혼동을 일으킬 수 있으므로 반성하겠습니다.
그리고 뜻과 맞지 않는 잘못된 용어를 만든 Bjarne Stroustrup 님도
반성해야 할듯 합니다;

그런데... 원 글을 올리신 분의 주된 의도는
<하나의 객체를 하나의 원자적인 연산으로 완성>하는 게 아니라
<객체를 생성할 때 필요한 과정을 빼먹는 걸 막고자 함>이었던 것
같습니다만...그렇다면 이미 자원 객체를 생성해 둔
다음 지형 객체를 생성하는 것은 문제가 없어 보입니다. 즉, 3단계로
지형을 생성하든, 1단계로 생성하든 그것이 지형 class를 가져다
쓰는 programmer가 실수할 여지가 없이 생성 가능하다면 상관없어 보이는데요.
이렇게 쓰다보니까, 문제가 new 에서 생성 단계를 거쳐 실수로 Load를 빼먹거나 해도, - 실제로 내용이 생성되는 부분은 LoadRawFile메서드 - 실제로 NULL 포인터가 아니니까 생성이 되었는지 아닌지를 판단하기가 애매하더군요. 코드를 읽어도 실제로 생성되는 부분이 new인지 아닌지도 애매하죠.
윗 글을 보아도 그런 것 같구요...맨 마지막 문장에선 생성 시점의 명확성을
추가로 요구하고 있지만 그건 지형 생성자가 끝나는 시점에 생성이 완료되게
해버리면 간단히 해결되니 큰 문제는 아닌 것 같습니다.
그렇다면 지형을 생성하는 과정 속에서 발생하는 문제를 얼만큼 정확히
손쉽게 잡아내느냐가 중요한 것 같습니다만...

음 원래 글을 올리신 분의 의도를 잘못 해석하고 있는 건가요ㅡ_ㅡa
쉬운 것은 올바르다.
Testors
Posts: 557
Joined: 2003-07-26 00:34
Location: (주)nFlavor
Contact:

Post by Testors »

수렁 wrote:그런데... 원 글을 올리신 분의 주된 의도는
<하나의 객체를 하나의 원자적인 연산으로 완성>하는 게 아니라
<객체를 생성할 때 필요한 과정을 빼먹는 걸 막고자 함>이었던 것
같습니다만...

.
.

음 원래 글을 올리신 분의 의도를 잘못 해석하고 있는 건가요ㅡ_ㅡa
수렁님과 저 둘중 한명은 잘못 해석하고 있는것이 틀림 없군요. :)
제가 볼때는..

요점은 '인스턴스의 무결성 보장' 이라고 생각합니다만..
'인스턴스가 존재는 하지만 사용해서는 안되는 상태' 가 없도록 하자는 거지요.

객체를 하나의 원자적 연산으로 '완성' 할 수 있다면 존재하는 인스턴스는 '사용가능한것' 이라는 의미입니다. 이것은 인스턴스의 무결성을 보장해 주므로 본래 목적을 달성하는 수단이 될 수 있습니다.

객체를 생성할때 필요한 과정을 빼먹는걸 막는것 역시 인스턴스의 무결성을 보장해 주는 수단이 될 수 있습니다. -- 생성자를 private 로 돌리고 XXX::CreateInstance() 를 이용하는 방법으로 구현할 수 있겠지요. 실패시 NULL 을 리턴하거나 예외를 던지게 되므로 인스턴스가 아예 생성되지 않습니다. --

제가 보기에는 얘기하신 두가지 모두 본래 의도에 부합하는 방법입니다.
수렁 wrote: 그렇다면 지형을 생성하는 과정 속에서 발생하는 문제를 얼만큼 정확히
손쉽게 잡아내느냐가 중요한 것 같습니다만...
에러 핸들링 문제는 현재 논의와 다른 주제입니다. 그래도 에러 핸들링에 대해 첨언하자면 제가 볼때는 -- 류광님도 위에 같은 내용을 포스팅 하신바 있지만 -- 원글쓰신분이 올리신 코드에서의 에러 핸들링이 CTerrain 클래스의 예외나 혹은 LoadXXX() 의 리턴값 수준의 검사로 끝나거나 혹은 그것에 의존한다면 그것은 넌센스입니다.

LoadXXX() 함수 내에서도 수많은 나름의 에러 보고루틴이 있어야 합니다. 실패했다면 파일이 없어서 실패한 것인지, 혹은 파일에 대한 read permission 이 없는 것인지, 혹은 파일이 invalid 한것인지를 알수 있어야 하겠지요.

끝으로...
인스턴스의 무결성 보장에 관한 논의는 충분히 의미가 있는 것이지만, 원저자분의 예제인 "CTerrain" 은 이에 관한 적절한 예라고 보기는 힘들것 같습니다. 그래서 논의가 흐려지고 있는듯한 인상을 받는데요.. 류광님이 "애초에 그런 원칙을 적용하고자 했던 게 잘못" 이라고 하셨듯이 확실히 "Terrain 관리 클래스" 라는 녀석은 Load Load New 가 아니고서는 무결성을 보장하기에는 너무나 거대하고 복잡한 녀석이거든요. -- 해서 저는 애초부터 예제는 무시하고 무결성 보장문제와 그 수단들에 대한 장단점에만 관심을 가졌더랩니다. :wink: --
플머/모델러/애니메이터 구해염 **현역/보충역 병특가능** / http://testors.net/
trackback:

트랙백

Post by trackback: »

류광의 번역 이야기: C++ RAII(Resource Acquisition Is Initialization)의 해석 RAII라는 이름 자체에 대한 "꿈보다 해몽" 조만간 나올 Effectiv Modern C++어딘가에 다음과 같은 문구가 나옵니다: [인용] (RAII 자체는 “Resource Acquisition Is Initialization”을 뜻하지만, 사실 이 기법의 핵심은 초기화가 아니라 파괴이다.) 관련해서 기억나는, 10년도 더 된 논의가 하나 있습니다: GpgStudy 포럼 “클래스 생성시 ... ” 특히 조순현 님의 답글과 제 답글이 이번 블로그..
Locked