C++/CLI의 Dispose Pattern에 대한 고찰

리소스 해제를 위한 .NET 개발환경에서 제공하는 Dispose 패턴을 파악하기 위해 테스트용으로 적용할 클래스 정의는 다음과 같으며 총 세가지의 경우로 시험을 해보았다.

ref class T : public IDisposable {
public:
    T() {
        Console::WriteLine(L"T() invoked");
    }

    ~T() {
        Console::WriteLine(L"~T() invoked");
    }

    !T() {
        Console::WriteLine(L"!T() invoked");
    }
};

첫번째 시험 코드는 마치 지역변수처럼 할당하는 경우이다. 하지만 절대 지역 변수가 아니라는 점.. CLR Heap에 할당된다.

int main(array ^args)
{
    T a;
    return 0;
}

실행 결과는 다음과 같다. 지역변수처럼 변수의 유효 Scope를 벗어나는 순간 소멸자가 호출되었다. 실제 IL에 의해 구현된 내부 흐름은 소멸자 호출이 아니라 IDisposable::Dispose 매서드의 호출이다. 즉, 소멸자가 IDisposable::Dispose의 재구현이다. 또한 내부적으로 GC에 의해 호출되어져야했을 Finalize 매서드는 호출되지 않게 조치된다. C#과는 다르게 Finalize가 호출되지 않도록 자동화되었다는 점이 매우 특이하다.

T() invoked
~T() invoked

두번째 시험 코드는 C++에서는 포인터 개념으로 생각되는, 즉 C++/CLI는 Handle 개념으로 CLR의 Heap에 객체를 생성하였고 delete를 호출하지 않은 경우이다.

int main(array ^args)
{
    T ^a = gcnew T();
    return 0;
}

실행 결과는 다음과 같다. Finalize에 해당하는 !T()가 호출되었다는 점에 유의하자.

T() invoked
!T() invoked

세번째 시험 코드는 두번째와 다르게 delete 연산자를 적용해 주었다.

int main(array ^args)
{
    T ^a = gcnew T();
    delete a;
    return 0;
}

실행 결과는 다음과 같다. Finalize가 아닌 Dispose가 호출되었다.

T() invoked
~T() invoked

여기서 얻을 수 있는 가장 중요한 한가지 결론은 ~T()에 해당하는 Dispose()와 !T()에 해당하는 Finalize()의 코드는 절대로 같이 호출되지 않는다는 점이다. C++/CLI의 사용자가 .NET의 참조형 변수를 지역변수처럼 사용하든지, gcnew에 의해 할당하여 사용하든지.. 또한 사용한 후 delete를 했든지, 하지 않았든지 간에 ~T()와 !T() 둘중에 하나는 반드시 실행되다는 점이다. ~T()는 기존의 C++ 개념으로써 호출되며 !T()는 .NET의 GC에 의해 호출된다. 즉, 리소스 해제를 위한 코드는 ~T()와 !T()에 똑 같이 중복적으로 와야한다고 생각한다.

마우스를 이용한 View 회전

예전에 나이스가이님이 질문한 마우스를 이용해 현재 화면을 회전하면서 화면상의 물체를 살펴보는 방법에 대해 간단하게 설명해 보려한다. 전문용어(?)로는 Arcball 기법이라고 하는데, 이 Arcball을 구현하는 방법으로 직접 회전행렬을 계산해서 OpenGL의 glMultMatrix를 이용해 적용하는 방법 하나와 x와 y축에 대한 회전각도만을 계산해서 glRotatef를 이용해 적용하는 방법이 있다. 첫번째 방법은 NeHe의 강좌에 소개되어져있으나 URL은 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=48 이다. 하지만 필자는 두번째 방법을 이용해 Arcball를 구현해 보려고 한다. 이유인 즉, 첫번째 방법은 OpenGL에서는 기본적으로 행렬 Type와 행렬간의 연산자 함수를 제공해주지 않으므로, 회전행렬을 직접 구하기 위한 행렬 Type을 개발자가 직접 정의해줘야 하고 행렬연산을 만들어줘야하는 번거로움이 있는 반면, 두번째 방법은 단지 x와 y축에 대한 회전각도 값을 실수형으로도 충분하기 때문이다.

글자만 있으면 썰렁하므로 별 의미도 없을법한 최종 결과 스크린샷은 아래와 같다.


화면상에 OpenGL의 GLUquadricObj을 이용하여 실린더를 6개를 그렸으며, 마우스를 이용하여 실린더 6개를 회전시키면서 살펴볼 수 있다. 보다 자세한 조작법은 마우스 오른쪽 버튼을 누른 상태에서 좌우로 마우스를 이동하면 Y축으로 회전되며 상하로 마우스를 이동하면 X축으로 회전한다. 이러한 사용자 액션과 반응을 염두해 두고 코드를 살펴보면 이해하는데 큰 도움이 될 것이다.

먼저 마우스 조작에 따른 Arcball 기법을 적용하기 이전에 화면상에 6개의 실린더를 그려주는 코드는 다음과 같다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -20.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		if(bToggle) glColor3f(0.6f, 0.6f, 1.0f);
		else glColor3f(1.0f, 0.6f, 0.6f);
		bToggle = !bToggle;

		glPushMatrix();
		glTranslatef(offset, 0.0f, 0.0f);
		glTranslatef(0.0f, 0.0f, -8.0f);
		gluCylinder(obj, 1.0f, 1.0f, 16.0f, 38, 4);
		glPopMatrix();
	}

	return TRUE;			
}

코드를 보고 이미 짐작하고 계실 분도 있겠지만, 이 코드는 NeHe의 강좌에서 제공하는 코드를 기본으로 하고 있다. 혹시 필요한 분들을 위해 전체코드를 다운로드 할 수 있도록 하겠다.

이제 위의 코드에서 마우스를 이용해 상하좌우로 회전시키면서 6개의 실린더를 구석구석 관찰하는 코드를 추가해보자.

먼저 마우스의 Drag를 통해 계산될 X축과 Y축에 대한 회전값을 위한 변수는 다음과 같다.

GLfloat xAngle;
GLfloat yAngle;
POINT mouseDownPt;

언급하지 않은 mouseDownPt 변수가 있는데, 이것은 마우스 버튼을 누른 좌표 지점을 저정하기 위한 변수이다. 마우스 버튼을 누른 지점과 마우스 버튼을 누른 상태에서 마우스를 이동하였을때 X축과 Y축으로 얼마만큼 이동되었는지를 계산하고 이 계산된 값을 이용해 xAngle와 yAngle값이 계산된다.

일단 xAngle와 yAngle가 계산되었다고 가정하고 최종적으로 회전을 적용시킨 코드는 가장 앞서 언급한 코드인 DrawGLScene 함수의 수정에 있다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();							
	glTranslatef(0.0f, 0.0f, -20.0f);

	glRotatef(xAngle, 1.0f,0.0f,0.0f);
	glRotatef(yAngle, 0.0f,1.0f,0.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		.
		.
		.
	}

	return TRUE;			
}

새롭게 추가된 오렌지색 두줄의 코드가 전부이다. X축과 Y축의 회전에 맞게 glRotatef의 함수의 인자가 사용되었음을 주의해서 보기 바란다.

이제 마지막으로 마우스의 액션에 따른 실제 xAngle와 yAngle의 값을 계산하는 방법을 알아보도록하자. 이 계산 코드는 마우스 액션에 대한 Event에 위치하게 되는데, MouseDown, MouseMove, MouseUp 이벤트에 그 코드가 존재한다.

	case WM_LBUTTONDOWN:
	{
		mouseDownPt.x = GET_X_LPARAM(lParam);
		mouseDownPt.y = GET_Y_LPARAM(lParam);

		SetCapture(hWnd);

		return 0;
	}

위의 코드는 간단히 앞에서 정의한 mouseDownPt 변수에 가장 처음 마우스 버튼이 눌려진 위치를 저장하고 있으며 SetCapture  API를 호출해서 마우스가 Window를 벗어나도 지속적으로 Mouse Message를 받을수 있도록 하고 있다.

	case WM_MOUSEMOVE:
	{
		if(GetCapture() == hWnd) {
			int X = GET_X_LPARAM(lParam);
			int Y = GET_Y_LPARAM(lParam);

			xAngle += (Y-mouseDownPt.y) / 3.6;
			yAngle += (X-mouseDownPt.x) / 3.6;

	                InvalidateRect(hWnd, NULL, FALSE);

			mouseDownPt.x = X;
			mouseDownPt.y = Y;
		}

		return 0;
	}

위의 코드는 마우스를 이동했을때 호출되는 코드인데, GetCapture를 이용해서 현재 마우스 버튼이 눌러졌는지를 검사하고, 만약 마우스가 눌려진 상태에서 움직였다면 바로 여기서 xAngle와 yAngle를 계산한다. 계산공식을 눈여겨 보길 바란다. 3.6으로 나눠주고 있는데, 이것은 마우스를 1 픽셀 이동했을 경우 1/3.6 회전, 즉 0.27777778도 회전하게 되는데, 이 회전량이 사용자가 가장 자연스럽게 느껴지므로 3.6으로 나눴다. 좀더 회전정도를 강하게 하고 싶다면 이 값을 줄이면 회전이 팍팍!! 되도록 할 수 있다.

	case WM_LBUTTONUP:
	{
		ReleaseCapture();
		return 0;
	}

끝으로 위의 코드는 마우스 버튼을 눌러 이동하면서 물체를 이리저러 살펴보다가 마우스 버튼을 누름을 해제시킬때 발생하는 코드로써, ReleaseCapture API를 호출한다. ReleaseCapture는 SetCapture를 호출한 윈도우가 반드시 호출해줘야 하는 Couple API로써 SetCapture에 의한 마우스 메세지의 독점처리를 해제하는 함수이다.

이상으로 간단하게 마우스를 이용한 Arcball 기법에 대해 살펴보는 것을 마치도록 하며, 아래에 해당 소스를 링크한다.

[펌] 고승덕 변호사, “남과 똑같이 해서는 절대 노력했다고 할 수 없다”

나는 외모에 컴플렉스가 있다. 아시겠지만, 대학교 때 고시 3개를 합격했다. 사법고시 합격, 외무고시 2등, 행정고시 1등, 그리고 서울대 법대를 수석 졸업했다. 학교 졸업 후 부모님께 큰 절을 했었다. 똑똑한 머리를 물려줘서가 아니라, 사실은 변변찮은 외모덕에 그저 고시에만 전념할 수 있게 해 줬기 때문에 감사하다는 의미로.

내 직업은 변호사이면서, 방송도 하고, 3년 전부터 책을 쓰고 있다. 평생 소원이 1년에 1권씩 평생 책을 내는 것이다. 글도 쓴다. 모 신문사에 경제기사를 1주일에 2개정도 쓴다. 또 오늘과 같은 특강도 한다. 평균 1주일에 2회 정도. 증권분야에서도 활동하고 있다. 운영하고 있는 사이트가 있는데 회원수만도 3만 5천명 정도 된다. 나름대로 홈페이지 관련 사업을 하나 구상중인 것도 있다. 마지막으로 대학에서 겸임교수로 활동하고 있다.

이처럼 내가 하고 있는 일이 무척 많은 것 같고 어떻게 이걸 다 할까 생각이 들겠지만 다 가능하다. 이 중에서 한가지만 하더라도 힘들다고 하는 사람이 많다. 그러나 가능하다고 생각하면 다 가능하다. 무엇이든지 목표가 중요하며, 그 목표 달성을 위해 가장 중요한 것은 확신이다. 사람들은 어려운 일일수록 확신을 갖지 못한다. 사람들은 나의 삶을 보면서 머리가 좋다고 생각한다. 그러나 사실은 전혀 그렇지 않다. 다 노력의 결과다.

학교 다닐 때 누구나 학원도 다니고 과외도 해 봤을 것이다. 고 2때 수학 45점의 낙제점수를 받은 적이 있다. 그 때 대학을 못 간다는 선생님의 말이 나에겐 큰 충격이었다. 그러나 집안이 그리 넉넉하지 않았기 때문에 과외를 한다는 것은 생각 할 수 없었다. 그래서 6개월간 죽어라고 했다. 그리고, 그 해 9월 2학기 때 400점 만점에 400점을 받았다. 그 이후로 매 시험마다 1등 했고, 석달에 한 번 정도 2등을 했었다. 학생시절 나는 여러 차례 내가 결코 남들보다 머리가 좋지 않다는 것을 깨달았고, 남보다 더 많은 노력을 해야 남만큼의 결과를 얻을 수 있다는 것을 알았다.

인생에 있어 2가지 자세가 있다.

보통 사람들은 남보다 적게 노력하고 결과는 남들과 같은 똑같이 나오게 하려고 한다. 사실은 이것이 경제학 법칙에 맞는 것이다. 투입을 적게하고 효과를 많이 내는 것. 반대로, 다른 사람들 만큼의 결과를 얻기 위해서는 더 많은 노력을 해야 한다고 생각하는 것이다. 나의 경험상으로 보면 후자가 훨씬 좋은 결과를 낳는다. 남보다 노력을 더 많이 해서 비슷한 결과를 가져 오는 것이 비효율적인 것 같지만, 실제로 이것은 어느 시점이 지나면 훨씬 더 좋은 결과를 가져온다.
이것이 내가 인생을 살면서 내 스스로 내린 결론이다. 그럼 노력이란 무엇이냐? 나는 “노력이란 성공의 확률을 높이는 것이다” 라고 정의 내린다. 하지만 물론 결과를 반드시 보장하지는 않는다. 노력에도 함수 관계가 성립한다.

노력 = f(시간 × 집중)

내가 실제 노력을 했는가 안 했는가를 판단하려면 시간을 많이 투입했거나 집중을 잘 했는가를 살펴보면 된다. 똑같은 일을 하더라도 3시간만에 끝내는 사람이 있는가 하면 5시간만에 끝내는 사람도 있다. 그러나 3시간만에 끝내는 사람이 실제 일을 더 잘하는 것 같지만 나머지 2시간을 어떻게 보내느냐가 중요하다. 더 많이 알기 위해 그 2시간을 투자하지 않았다면 노력하지 않은 것이다.

집중에 대해서 얘기해 보면, 고시 공부할 때 예를 들어 보겠다. 나는 고시 공부를 1년간 해서 합격했다. 어떻게 가능 했느냐? 첫째는 된다고 생각하는 확신이 있었기 때문이고, 둘째는 남보다 더 많은 노력을 했기 때문이다. 보통 고시에 합격하려면, 봐야 할 책이 50권, 권당 페이지는 500P, 그 책을 5번을 봐야 합격하다는 얘기가 있다. 그러나 나는 7번을 보았다. 이를 계산해 보면 50 × 500 × 7 = 175,000 페이지를 읽어야 한다는 얘기다. 이것을 1년을 360일로 계산해보면 1일 목표량이 나온다. 즉, 1일 500 페이지 정도의 분량을 봐야 한다는 계산이다.

이처럼, 목표를 세울 때는 구체적으로 세워야 한다. 막연한 목표는 달성하기 힘들다. 이 결론을 보면 “인간이 할 짓이 아니다” 라고 생각할 것이다. 누구나 그렇게 생각한다. 그렇게 생각하면 사람들은 포기하게 된다. 설사 하게 되더라도 하다가 흐지부지 된다. 이렇게 목표에 대해 확신이 없고, 목표를 의심하는 사람은 집중을 할 수 없다. 무엇보다도 자신의 목표에 확신을 가져라.

된다는 사람만 되고 안 된다고 생각하는 사람은 안 된다. 일단 안 된다고 생각하는 대부분의 85%의 사람들은 이미 나의 경쟁상대가 아닌 것이다. 된다고 생각하는 일부만 나의 경쟁이 된다. 그럼 경쟁대상이 줄어드니 훨씬 마음도 한결 가벼워 진다. 세상도 절대적으로 잘 하는 사람은 원하지도 않고 필요하지도 않다. 남 보다만 잘 하면 된다. 그럼, 다른 사람보다 잘 하고 있는지를 어떻게 판단하느냐? 그것은 나 자신을 판단 기준으로 삼으면 된다. 인간은 거의 비슷하다. 내가 하고 싶은 선에서 멈추면 남들도 그 선에서 멈춘다. 남들보다 약간의 괴로움이 추가되었을 때라야 비로소 노력이란 것을 했다고 할 수 있다.

고시 공부할 때 7시간 잤다. 장기간 공부를 해야 할 경우라면 일단 잠은 충분히 자야 한다. 하루 24시간 중 나머지 17시간이 중요하다. 고시생의 평균 1일 공부시간은 10시간 정도다. 그러나 정말 열심히 하는 사람은 잠자는 시간 빼고 17시간을 하는 사람이 있을 것이다라는 생각을 했다. 그러면 정말, 밥 먹는 시간도 아까웠다. 남들과 똑같이 먹어서는 안 된다고 생각한 것이다. 반찬 떠 먹는 시간도 아까웠다. 씹는 시간도 아까웠다. 그래서 모든 반찬을 밥알 크기로 으깨어 밥과 비벼 최대한의 씹는 시간도 아꼈다. 숟가락을 놓는 그 순간부터 공부는 항상 계속 되어야 했다. 나의 경쟁자가 설마 이렇게까지 하겠냐 하고 생각들면 노력했다고 할 수 있는 것이다.

미국에서 생활 할 때 보면 소위 미국의 전문가라고 하는 사람들은 간단한 샌드위치로 끼니를 때운다. 점심시간 1시간 다 쓰고, 이래저래 20~30분 또 그냥 보내는 우리 나라 사람들은 그들에 비하면 일 하는게 아니다. 집중을 잘 하는 것은 벼락치기 하는 것이다. 벼락치기 할 때가 더 기억에 오래 남는다고 한다. 우등생은 평소에 벼락치기 하는 마음으로 공부를 한다. 이렇게 할 수 있는 이유는 목표가 분명하기 때문이다. 막연한 목표를 가지면 이렇게 긴장이 안되지만 분명하면 항상 긴장되고 집중을 잘 할 수 있다.

방송하면서 인생이 많이 바뀌었다. 처음 주변 사람들은 말렸지만 결과적으로 보면 좋은 결과를 가져왔다. 나는 세상을 살면서 이런 생각을 해 본다. 사람은 해야 할 일과 하지 말아야 할 일이 있다. 사람이 해야 할 일이란 남에게 해를 끼치는 일이 아니면 해도 되는 일이다 라고 생각한다. 그렇게 생각하면 세상에 해야 할 일이 참 많다.

나에게는 인생 철학이 있다. 인생을 살다 보면 A와 B가 있을 때 나는 A가 더 중요하지만 B를 선택해야 할 경우가 많다. 그럴 때 어떤 것을 선택하느냐는 매우 중요하다. 학교 다닐 때 나는 A는 여자친구였고, B는 고시 합격 이었다. 대학시절 한 때 A는 내게 무척 중요한 시기가 있었다. 여기서 내가 말하는 t1,t2판단법이란게 중요하다. 내가 A를 선택하면 난 B를 성공할 수 있는 확률은 줄어든다. 그러나, 나의 외모 컴플렉스 때문에 A를 성공하는 일 또한 확신이 없었다. 그래서 나는 B를 먼저 해서 좀 더 유리한 조건이 되면 A도 이룰 수 있다고 생각했다. 그렇게 결론을 내리면 고시합격을 더 빨리 해야 할 필요성을 느끼게 되어 집중도 잘 되었다. 이것이 내가 인생을 살아가면서 느낀 것이다. 장기간 동안 시간의 흐름을 계산해 볼 때 무엇을 먼저 해야 하는가를 판단하는 것은 매우 중요하다.

그래서 난 남들이 말려도 우스꽝스러운 모습으로 코미디 프로에도 나갈 수 있었다. 난 ” 할 수 있을 때 뭐든지 해 버리자 ” 라는 생각으로 할 수 있는 건 다 한다. 그러면서 인생에서 내가 할 수 있는 일들을 쌓아 가면 된다. 하다가 안되면 포기하더라도 아예 안 하는 것보다는 낫다. 아예 하지 않으면 할 수 있는 일은 아무 것도 없다. 나의 징크스는 시험에 합격하려면 10번을 봐야 하는 것이다. 그래야 합격의 확신을 갖는다. 3~4번만 보면 불안하다. 그래서 그냥 뭐든지 기본적으로 10번을 본다. 몇 번 3~4번 책을 보고 시험을 본 적 있다. 역시 떨어졌다.

앞으로는 이렇게 해 보자. 첫째는 남보다 많이 노력하는 것이다. 둘째는 어려운 목표일수록 확신을 가져 보자. 그러면 정말 되는 일이 훨씬 많다. 셋째는 남보다 최소 3배는 해야 한다고 생각하자. 직장에서 윗 사람이 일을 시킬 때 남보다 더 많은 일을 시키고, 나한테만 어려운 일을 시키더라도 신나는 표정을 지어보자. 대부분의 사람, 아니 나의 경쟁자는 이럴 때 얼굴을 찌푸릴 것이다. 그러나 내가 이기려면 그들 보다는 다른 모습이어야 한다. 힘들더라도 괴로움을 추가해 보자. 남들에 비해 노력한 만큼의 결과가 나오지 않더라도 노력을 계속해야 한다. 3배의 노력만 한다면 4번째 부터는 분명 가속도가 붙어 급속도로 차이가 날 것이다.

마지막으로 대인관계에 대해 강조하고 싶다. 세상을 살다보면 대인관계를 유지하는 것도 노력이다. 성공을 위해서는 나 혼자의 노력 외에 대인관계가 차지하는 비중이 높은 경우가 있다. 어떤 상대를 만나든 최소 5분은 상대방을 위해 생각하는 시간으로 할애해 보자.

남과 똑같이 해서는 절대 노력했다고 할 수 없다.