프로그래밍/OpenGL2009. 4. 23. 16:20

1. OpenGL의 좌표계

- 오른손 좌표계를 사용함

1.1 Viewport

- 논리적 좌표를 물리적인 화면 픽셀 좌표로 전환하는 과정



1.2 Local coordinate system

- 3D에서 object는 정점(vertex) 리스트로 구성된다. 그 정점들을 기준좌표(0, 0, 0)으로 부터의 거리. 즉, 상대적 좌표리스트로 구성됨.

1.3 World coordinate system

- Local coordinate system으로 구성된 오브젝트를 실제의 위치상에 배치하기 위해서는 월드상의 좌표계로 이동하여 계산하도록 한다.


1.4 Camera coordinate system

- 카메라가 보고 있는 위치에 따라 오브젝트가 다르게 보이므로 최종적으로 카메라좌표를 기준으로 각 오브젝트들의 위치계산을 해야 한다.

- 관측지점과 관측 방향을 알아야 함.

 

2. 투영

- 3D 좌표계 내에서 만들어진 지오메트리들을 편평한 2D 공간으로 옮기는 과정.


2.1 직교 투영(orthographic projection)

- 정사각형 또는 직사각형의 영역 내에 모든 것이 투영.

- 그 영역을 벗어나는 것들은 그려지지 않는다.

- 영역 내에 있는 모든 물체들이 거리에 관계없이 모두 같은 크기 비율로 표현


2.2 원근 투영(perspective projection)

- 물체의 거리에 따라서 2D 화면에 나타나는 크기가 달라짐


3. OpenGL Rendering Pipeline

 

3.1 Display list

- 기하학적인 정보를 나타내는 데이터든, 픽셀을 나타내는 데이터든 상관없이 모든 데이터들을 display list에 저장해둘 수 있다.

3.1.1 Display list를 사용하는 이유

- OpenGL 커맨드들을 나중에 다시 사용하기 위해서 저장하는 데 사용되기 때문에 성능을 향상시켜 줄 수 있다.

3.2 평가자

- 모든 기하 프리미티브들은 결국 정점으로 표현된다. 매개변수 곡선이나 곡면은 기저 함수라고 불리는 다항 함수와 제어점들로 표현된다. 평가자를 사용하면, 제어점으로부터 곡면을 표현하는 데 사용되는 정점들을 구할 수 있다. 이때 다항식 매핑(polynomial mapping)이라는 기법을 사용하면, 제어점으로부터 표면 법선(surface normal), 텍스처 좌표, 컬러, 공간 좌표 등을 얻을 수 있다.

- 제어점만으로 커브나 표면상의 임의의 점을 지정 하여, 커브나 표면 렌더링 가능.

3.3 정점 연산(Per-Vertex Operation)

- 공간 좌표와 같은 일부 정점 데이터들을 4X4 부동 소수점 행렬로 변환.

- 3D 공간의 한 지점을 나타내던 공간 좌표를 스크린상의 한 지점으로 투영 시킨다.

 

3.4 Primitive Assembly

- 주된 부분은 clipping 수행.

3.5 Rasterization

- 기본도형에 속한 영역을 한 줄씩 픽셀들로 채우는 과정

- 기하 데이터와 픽셀 모이터를 모두 fragment로 변환한다.

- 각각의 fragment들은 사각형으로 되어 있으며, 프레임버퍼의 한 픽셀에 대응된다.


3.6 Fragment 연산

- 특정한 fragment 연산을 tngiod하여 특정한 fragment들을 변경하거나 제거한다

- 가장 먼저 수행하는 연산은 텍스처 메모리로부터 각각의 fragment에 대한 텍셀(texture element)를 생성하여 해당 fragment에 적용하는 텍스처링이다. 텍스처링 이후 fog연산과 scissor 테스트, 알파 테스트, 스텐실 테스트, 깊이 버퍼 테스트 등을 차례로 수행

※ scissor 테스트 : 응용 프로그램은 뷰포트 안에 가위 사각형 이라고 하는 사각형 영역을 지정할 수 있다. 그 사각형은 렌더링이 일어나는 영역을 결정한다

'프로그래밍 > OpenGL' 카테고리의 다른 글

옥트리(octree)  (0) 2009.04.25
OpenGL Viewing  (0) 2009.04.24
Picking 사용하기  (0) 2009.04.22
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
void glutPostRedisplay(void)  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 22. 16:01


구체적 클래스를 정의하지 않고도 서로 관련성이 있거나
독립적인 여러 객체의 군을 생성하기 위한 인터페이스 제공

 정의: 추상적인 부품을 조립해서 추상적인 제품을 만드는 추상적인 공장 같은 패턴
       : 클라이언트에서 구상 클래스를 지정하지 않으면서도 일군의 객체를 생성할 수 있도록 하는 패턴

활용:
         1. 생성되고 구성되고 표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때
         2. 하나 이상의 제품군들 중 하나를 선택해서 시스템을 설정해야 하고 한번 구성한 제품을
            다른 것으로 대체 가능할 때
         3. 관련된 객체군을 함께 사용해서 시스템을 설계하고,
            이 제품이 갖는 제약사항을 따라야 할 때
         4. 제품에 대한 클래스 라이브러리를 제공하고,
            그들의 구현이 아닌 인터페이스를 표현하고 싶을 때
결과:
       1.구체적인 클래스를 분리한다  
         추성클래스가 정의한 인터페이스를 통해서만 클라이언트 프로그램을 작성한다
       2. 제품군을 쉽게 대체할 수 있도록 한다
       3. 제품간의 일관성을 증진한다
       4. 새로운 종류의 제품을 제공하기 어렵다 –
          새로운 제품에 대해 추상 팩토리를 확장하는 것이 어렵다

단점: 클래스 계층이 커질 수 있다

예제코드

#include 
#include 

#include 
#include 

class AbstractProdcutA
{
public:
 AbstractProdcutA ()         {}
 virtual ~AbstractProdcutA() {}
};

class AbstractProdcutB
{
public:
 AbstractProdcutB ()         {}
 virtual ~AbstractProdcutB() {}
};

class AbstractFactory
{
public:
 virtual AbstractProdcutA* CreateProductA (void) = 0;
 virtual AbstractProdcutB* CreateProductB (void) = 0;
};

class ProdcutA1 : public AbstractProdcutA
{
public:
 ProdcutA1()
 {
  std::cout << "ProdcutA1()" << std::endl;
 }
};
class ProdcutA2 : public AbstractProdcutA
{
public:
 ProdcutA2()
 {
  std::cout << "ProdcutA2()" << std::endl;
 }
};
class ProdcutB1 : public AbstractProdcutB
{
public:
 ProdcutB1()
 {
  std::cout << "ProdcutB1()" << std::endl;
 }
};
class ProdcutB2 : public AbstractProdcutB
{
public:
 ProdcutB2()
 {
  std::cout << "ProdcutB2()" << std::endl;
 }
};

class ConcreteFactory1 : public AbstractFactory
{
public:
 virtual AbstractProdcutA* CreateProductA (void)
 {
  return new ProdcutA1();
 }
 virtual AbstractProdcutB* CreateProductB (void)
 {
  return new ProdcutB1();
 }
};

class ConcreteFactory2 : public AbstractFactory
{
 virtual AbstractProdcutA* CreateProductA (void)
 {
  return new ProdcutA2();
 }
 virtual AbstractProdcutB* CreateProductB (void)
 {
  return new ProdcutB2();
 }
};


class Client
{
public:
 void Create (AbstractFactory *f)
 {
  ProdcutA_.reset( f->CreateProductA() );
  ProdcutB_.reset( f->CreateProductB() );
 }

private:
 std::auto_ptr ProdcutA_;
 std::auto_ptr ProdcutB_;
};


int _tmain(int argc, _TCHAR* argv[])
{
 Client client;


 std::cout << "by ConcreteFactory1" << std::endl;
 client.Create ( &ConcreteFactory1() );

 std::cout << std::endl;

 std::cout << "by ConcreteFactory2" << std::endl;
 client.Create ( &ConcreteFactory2() );

 return 0;
}

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
PVOID  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/OpenGL2009. 4. 22. 15:19

1) Picking의 원리
우리가 화면에서 무엇인가를 선택할때는 화면을 보고 그 위에 마우스를 올리고 클릭한다. 그러나 컴퓨터는
멍청해서 이게 어디인지 모른다. 그래서 화면을 다시 그리면서 그리는 붓에 마우스가 걸리면 그제야 그게 어딘
지 알게된다. 예를들어 아래 표에서 화살표 위치를 픽킹했다면 컴터는 화면을 다 다시 그리면서 저 칸에 그림
을 그리게 되면 "엇!! 여기 마우스가 있었군!!" 하고 알게된다.

위와 같은 원리로 픽킹을 하게 되므로 다시 그릴 때 마우스 위치에 걸리면 현재 자기가 무엇을 그리는지를
알아야 되고(Name을 이용) 검사할 칸의 크기를 알아야 되고(int형으로 가로세로 픽셀수를 정해줌) 몇 개가 어
느 위치에 그릴 때 선택되었는지(버퍼)를 알아야 한다.

2) Picking 예제

 

//픽킹을 위한 버퍼의 크기인데 넉넉히 줘도 된다. 모자라면 안된다.
#define BUFFER_LENGTH 64
//픽킹으로 사용되는 입력은 마우스의 좌표이다.
void PickingPlane(int mx, int my)
{
GLuint selectBuff[BUFFER_LENGTH];
GLint hits, viewport[4];
glSelectBuffer(BUFFER_LENGTH, selectBuff); //Picking에 사용될 버퍼를 설정한다.
glGetIntegerv(GL_VIEWPORT, viewport);//변환에 필요한 정보인 viewport정보를 얻어온다.
//여기서 부터는 랜더링이 화면에 되지 않는다. 즉, 앞에서 말한것처럼 컴퓨터가 마우스가 어디서
//걸리는지 찾기 위해서 다시 혼자 그려보는 그런 단계가 시작된다.
glRenderMode(GL_SELECT);
//현재의 프로젝션 메트릭스 저장.
//지금부터 설정되는 메트릭스의 순서에 유의해야 한다. 기본이 Modelview인데 새로 프로젝션을 설정하는데
//이후에 생긴 변환이 현재 사용하는 변환에 영향을 주지 않도록 push matrix를 하는것이다.
glMatrixMode(GL_PROJECTION);
glPushMatrix();//저장.
//랜더링과 같은 환경을 구현하기 위해서 똑같이 다시 그릴수 있는 변환을 한다.
glLoadIdentity();
gluPickMatrix(mx,viewport[3]-my,2,2,viewport);
int w = viewport[2], h = viewport[3];
if(w<=h)
glOrtho(-BASE_UNIT,BASE_UNIT,
-BASE_UNIT*(GLfloat)h/(GLfloat)w,BASE_UNIT*(GLfloat)h/(GLfloat)w
,-BASE_UNIT,BASE_UNIT);
else
glOrtho(-BASE_UNIT*(GLfloat)w/(GLfloat)h,BASE_UNIT*(GLfloat)w/(GLfloat)h,
-BASE_UNIT,BASE_UNIT,
-BASE_UNIT,BASE_UNIT);
//똑같이 그리기 위해 변환과 네임을 쓰는건 다 그려준다.
glMatrixMode(GL_MODELVIEW);
glPushMatrix();//다른 부분에 영향을 주지 않도록 메트릭스 저장
RenderInit();
//다 똑같은데 이 함수만 다르다. 즉, 이름을 정해가면서 그린다.(뒤에서 다룸)
DrawCube(BASE_UNIT,true);
glPopMatrix();
//이상은 네임을 로드/푸시하는것 말고는 보통 상태에서 그리는 것과 같다.
//앞에서 저장한 프로젝션 메트릭스를 복구하기 위해 프로젝션으로 변경.
glMatrixMode(GL_PROJECTION);
glPopMatrix();//복구
//랜더링을 모두 실행.- 이제 어딘지 검사하기 위해서 OpenGL혼자 그려본 것이 끝났다.
//그럼 어디서 걸렸는지 녀석에게 물어보자.
glFlush();
//Picknig을 종료하면서 스택을 얻어온다. 즉, 녀석에게 니가 아는걸 알려달라고 하는 것과 같다.
hits = glRenderMode(GL_RENDER);
//다시 그림을 그리기 위해서 모델뷰로 변경한다. 이제부터는 다시 화면에 그려진다.
glMatrixMode(GL_MODELVIEW);
}

'프로그래밍 > OpenGL' 카테고리의 다른 글

옥트리(octree)  (0) 2009.04.25
OpenGL Viewing  (0) 2009.04.24
OpenGL 기초  (1) 2009.04.23
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
void glutPostRedisplay(void)  (0) 2009.04.22
Posted by 마블(이환문)

STL 사용시 벡터 사용시 이런 에러가 났다

에러 코드 : IntVector.insert(&IntVector[3], 11)

해결방안:

 vector<int>::iterator t = IntVector.begin();
 advance(t, 3);
 IntVector.insert(t, 11);

advance()는 반복자를 지정한 숫자만큼 이동시켜주는 함수이다

Posted by 마블(이환문)

Run-Time Check Failure #2 - Stack around the variable '변수이름' was corrupted

사용자가 설정한 배열의 크기보다 더 큰것을 넣을려고 할때  위와 같은 에러가 발생

Posted by 마블(이환문)

소켓 프로그램을 따라하다보니 TRACE() 함수가 있었다....

TRACE가 어느 헤더 파일에 들어 있는지 몰라서 검색을 하였지만.. 

결과는 나오지가 않았다..

이는 #define으로 선언해줘야 했던 것이다

예전에는 이 방법으로 사용하였지만..

#define TRACE 1 ? 0 : OutputDebugString

요즘에는 이렇게 사용한다..

#define TRACE __noop

__noop는 인자를 모두 무시하는 기능을 한다고 한다

Posted by 마블(이환문)

HapticInit.obj : error LNK2005: "struct HINSTANCE__ * g_hInstance" (?g_hInstance@@3PAUHINSTANCE__@@A)이(가) Haptic.obj에 이미 정의되어 있습니다.

 

이미 다른 cpp 파일에 선언되어 있어서 나는 에러다

 

2005 에러는 변수, 함수, 클래스 등이 중복되어 선언되서 그런거다

Posted by 마블(이환문)

<고전적인 메시지 처리>
◈ 윈도우 프로시저의 switch-case문
문제점
- 메시지가 많아지면 case가 많아져 코드가 길어진다. 가독성이 떨어지고 자원이 낭비된다
- case에서 사용하는 변수가 많아지면 프로시저가 호출될 때마다 스택의 낭비가 심하고 불필요한 메모리를 소모한다

<개별 메시지 처리 함수>
- 메시지 처리 코드는 처리 함수를 만들고 WndProc안에서는 함수 호출만 한다
- 메시지 처리 함수를 메시지 핸들러라 한다
- 가독성이 높다
- 함수 내부 수정이 용이하며 다중 루프 탈출도 편하고 코드 재사용성이 높다

<메시지 크래커>
- WndProc에 들어갈 함수 호출문과 메시지 처리 함수의 원형을 매크로로 잘 정의해 놓은 것

<Windowsx.h>
- 메시지 크래커는 컴파일러가 지원하는 것도 아니고 C언어 명세에 있는 것도 아니다
- Windowsx.h 헤더 파일에 정의되어 있는 단순한 매크로 구문이다
HANDLE_MSG(윈도우, 메시지, 처리함수)
- 윈도우에서 발생하는 메시지와 처리 함수를 짝짓기한다
windowsx.h
#define HANDLE_MSG(hwnd, message, fn) case (message) : return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
- HANDLE_##message 매크로 함수는 메시지별로 정의되어 있다
CF)##은 치환후 주변의 공백과 함께 스스로 제거된다
메시지 크래커 적용
- WndProc에서 처리하고자 하는 메시지에 대해 HANDLE_MSG 매크로 구문을 삽입한다. 처리하고자 하는 메시지와 함수명을 짝짖기 하면 된다
- 함수의 본체를 만든다. 메시지 처리 함수의 원형은 windowsx.h에 주석으로 기록되어 있다
- 핸들러 함수의 원형을 선언한다. windowsx.h를 포함시킨다
 
 
 
=================================================
 
예제 코드
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
       HANDLE_MSG(hwnd, WM_SIZE, OnSize);
       HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
       HANDLE_MSG(hwnd,WM_DESTROY, OnDestroy);
       return DefWindowProc(hwnd, message, wParam, lParam);
}
 
void OnSize(HWND hwnd, UINT state, int cx, int cy)
{
       g_cx = cx;
       g_cy = cy;
}
 
void Onpaint(HWND hwnd)
{
       HDC       hdc;
       PAINTSTRUCT   ps;
 
       hdc = BeginPaint(hwnd, &ps);
 
       char  szString[30];
       wsprintf(szString, "너비 : %d, 높이 : %d", g_cx, g_cy );
       TextOut(hdc, 0, 0, szString, lstrlen(szString) );
 
       EndPaint ( hwnd, &ps );
}
 
void OnDestroy(HWND hwnd)
{
       PostQuitMessage(0);
}
 
갈색으로 된 부분을 보면  윈드프록 함수를 간결하게 그리고 메시지 처리를 함수로 빼어 내서
훨씬 알아보기도 쉽고 사용하기 편하게 보입니다.
 
 
HANDLE_MSG 매크로는 windowsx.h 파일에 정의 된 매크로인데
 
#define HANDLE_MSG(hwnd, message, fn) \ case (message)" return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
이렇게 정의 되어 있습니다
 
HANDLE_MSG(hwnd, WM_SIZE, OnSize); 함수가 호출 되면 'message' 에 WM_SIZE가 전달이
되어
 
 case (WM_SIZE) : return HANDLE_##message((hwnd),(wParam), (lParam), (OnSize))
 
가 호출이 되게 됩니다.
##연산자는  #왼쪽과 ## 오른쪽을 연결해서 하나의 문자열로 만드는데 'message'에 WM_SIZE가 호출 되었으므로 결국
 
case (WM_SIZE): return HANDLE_WM_SIZE((hwnd), (wParam), (lParam), (OnSize))
가 되게 됩니다.
 
결국 HANDLE_MSG(hwnd, WM_SIZE, OnSize);  함수가 실행 되면
 
windowsx.h 파일내에 있는
 
#define HANDLE_WM_SIZE(hwnd, wParam, lParam, OnSize) \
             OnSize(hwnd, wParam, LOWORD(lParam), HIWORD(lParam)), 0L
이 호출 됩니다.
 
좀복잡했지만 정리하면
 
 
 
HANDLE_MSG(hwnd, WM_SIZE, OnSize);
이 코드는 바뀌고 바껴서
 
case WM_SIZE:
       OnSize(hwnd, wParam, LOWORD(lParam), HIWORD(lParam));
       return 0;
 
이렇게 바뀌게 되는 겁니다.  결국 저 간단한 코드로 OnSize 함수를 실행 시키게 되는 겁니다.
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 22. 15:18

1. PVOID

PVOID란 void 포인터, 즉 void *을 말합니다. 이 포인터는 C언어에서는 굉장히 막강한 능력을 가지고 있죠. 어떠한 능력인고 하니 모든 다른 포인터를 가리킬 수 있으며, 다른 모든 포인터로 자동적으로 캐스팅이 이루어진는 점 입니다. 따라서 C언어에서 일반화 코드 내지는 자료구조의 코드등에 약방감초처럼 등장하는 놈이 PVOID라 할 수 있겠습니다.

그럼 간단히 PVOID의 능력을 살펴보고 넘어가도록 하겠습니다.

int a = 3; 
PVOID pv = &a; 

C언어에서는 매우 합법적인 코드입니다. pv는 모든 포인터를 가리킬 수 있으므로, 당연히 int의 포인터도 가리킬 수 있는 것입니다.

int *pi = pv; 

이또한 아주 합법적인 코드입니다. C언어에서 PVOID는 다른 포인터로 자동적으로 승격되기 때문이죠.

여기서 중요한 코드를 한번 살펴 보도록 합시다.

int a = 3; 
int *pi = &a; 
PVOID pv = &a; 

자 위와 같은 코드가 있다고 했을때 우리가 pi의 값을 읽기 위해서는 역참조 연산자 *를 사용해서 *pi로 읽습니다. 그러면 a에 저장되어 있는 값인 3이 나오게 되는 것이죠. 그렇다면 PVOID를 역참조하기 위해서 *pv라고 하면 어떻게 될까요? 그러면 컴파일에러가 발생하게 됩니다. 왜일까? 왜 역참조를 하지 못하는 것일까요?

+---+---+---+---+ 
|   |   |   |   | 
+---+---+---+---+ 

일반적인 32비트 컴퓨터에서 int는 위와 같은 4바이트로 이루어집니다. 여기서 int의 포인터라는 것이 가지고 있는 정보는 두가지입니다. 위의 메모리가 시작되는 주소와 함께, 한 덩어리 크기가 4바이트라는 것입니다.

A                 B                 C 
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+ 
|FA |0C |2B |34 | |FA |0C |2B |34 | |FA |0C |2B |34 | 
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+ 

편의상 메모리에 데이터가 위와 같이 저장되어 있다고 가정해 봅시다. A,B,C는 각각 주소를 나타냅니다. int의 포인터가 A라는 위치를 가리킬때, 해당 데이터를 읽게되면 그 위치부터의 4바이트를 읽어서 FA 0C 2B 34라는 값이 나오게 됩니다. 그렇다면 해당 포인터에 1을 더하면 어떻게 될까요? 그러면 해당 포인터는 B지점으로 이동합니다. 왜냐하면 int가 가리키는 놈은 4바이트라는 것을 알고 있으므로 다음 데이터를 읽기 위해서 4바이트 뒤로 이동하는 것이죠.

여기서 우리는 PVOID가 역참조를 하지 못하는 이유를 알 수 있습니다. PVOID는 모든 것을 가리킬 수 있다는 사실에서... 그 놈이 가리키는 놈의 크기를 알지 못하는 것입니다. 즉, 다른 포인터는 모두 번지와 함께 대상체의 크기 정보를 가지고 있는 반면에 PVOID는 대상체의 크기 정보는 없이 단순히 번지만 가지고 있는 것이죠. 따라서 PVOID는 포인터의 역참조 연산을 비롯해 +,-연산도 이루어질 수 없습니다.

그러므로 또한 우리가 코드상에서 PVOID를 통해서 일반화를 시킬 경우는 항상 크기를 같이 저장하거나, 아니면 포인터를 읽고/쓰는 부분을 콜백으로 처리하는 등의 형태가 됩니다. 즉, 다음과 같은 swap함수의 원형이 나올 수 없다는 이야기가되죠.

void swap(PVOID pa, PVOID pb); 

PVOID만으로는 절대로 swap을 할 수가 없습니다. 왜냐하면 크기 정보가 없기 때문이죠. 물론 프로그래머가 PVOID가 가지고 오는 포인터가 무조건 int의 포인터라고 가정하면 저런 원형도 나올 수 있습니다.

void swap(PVOID pa, PVOID pb) 
{ 
    int *pc = pa; 
    int *pd = pb; 
    
    ... 
} 

하지만 위의 코드의 swap함수는 일반화가 안된 그저 int값만 교환하는 swap함수와 다를 바가 없습니다. 따라서 C에서 일반화된 코드를 작성하기 위해서 PVOID를 선택한 경우는 그 사이즈또한 같이 인수로 받아야 합니다.

void swap(PVOID pa, PVOID pb, size_t size); 

이제야 비로서 사이즈에 맞는 swap을 할 수 있는 함수가 되었습니다.

이번 장에서 가장 중요하게 기억해야 할 점은 PVOID는 모든 포인터를 가리킬 수 있다는 점, 다른 포인터로 자동 승격된다는 점. 그리고 PVOID내에는 크기 정보가 없다는 점입니다.

2. 템플릿

그럼 이번에는 템플릿을 한번 짚고 넘어가 보도록 하겠습니다. 템플릿 또한 일반화 프로그램의 대명사와 같은 놈이므로 앞장에 나온 PVOID와 비슷하다고 생각할 수 있습니다. 하지만 일반화 코드를 만들때 사용한다는 점 왜에는 전혀 똑같이 않습니다. 실제로 비교 대상도 아니고, 템플릿의 내부 구현이 PVOID와 상관이 있지도 않습니다. 그럼 이제 템플릿을 한번 살펴보겠습니다.

템플릿의 기본 아이디어는 코드를 상세화 하지 않고 그 형틀만 만든다는 것입니다. 즉, 형틀만 만들어 두고 컴파일하는 시점에 작은것이 필요하면 형틀에서 작은 놈을 찍어내고, 큰것이 필요하면 형틀에서 큰것을 찍어내는 구조와 같습니다.

그럼 앞장에서 보였던 swap함수를 이번에는 템플릿으로 한번 작성해 보겠습니다.

template 
void swap(T &a, T &b) 
{ 
    T c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

위가 템플릿의 swap함수입니다. 여기서 변화 가능한 부분은 T입니다. 형틀에서 나머지는 모두 고정되어 있되, T만 바뀔 수 있습니다.

int a, b; 
swap(a,b); 

위와 같이 호출했다고 가정해봅시다. 그럼 컴파일러는 생각을 합니다. "음 요놈이 int형으로 호출했네. 그럼 내가 int형 함수를 하나 만들어야 겠군. " 같은 것을 떠올리겠죠.

void swap(int &a, int &b) 
{ 
    int c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

그러면서 위 함수를 형틀에서 찍어냅니다.

이번에는 아래와 같이 호출했다고 해봅시다.

double a,b; 
swap(a,b); 

그럼 컴파일러는, "어? double 버전도 필요한가 보네"... 하고는 함수를 하나 더 만듭니다.

void swap(double &a, double &b) 
{ 
    double c; 
    
    c = a; 
    a = b; 
    b = c; 
} 

자, 그럼 결국 함수는 두개가 된 셈입니다. 프로그래머는 함수를 하나 만들었지만 컴파일타임에 컴파일러가 보고 함수를 필요한 만큼 찍어내는 것이 템플릿의 기본 전략인 셈이죠.

이번 장에서 기억해야 할 점은 세가지입니다.

  • 1. C++에서 일반화 프로그래밍에 템플릿을 사용할 수 있다.
  • 2. 템플릿은 PVOID와 관련이 없다.
  • 3. 템플릿의 구현의 핵심은 컴파일 타임에 필요한 만큼 함수를 생성하는데 있다.

3. PVOID와 템플릿...

이번 장에서는 실제 어셈블된 코드를 통해서 어떠한 방식의 접근 법이 어떠한 장점과 단점을 가지는지 알아 보도록 합시다.

먼저 아래코드는 C언어 버전입니다. 편리한대로 PVOID를 void*로 작성했습니다. 또한 어셈블해서 코드를 살펴 볼 것이므로 최대한 간단하게 작성했습니다. 모든 에러와 예외는 없다는 가정하에서 작성되었다는 점을 기억해 주십시요.

#include  
#include  

void swap(void *pa, void *pb, size_t size) 
{ 
       void *pm = malloc(size); 
        
       memcpy(pm, pa, size); 
       memcpy(pa, pb, size); 
       memcpy(pb, pm, size); 
        
       free(pm); 
} 

int main() 
{ 
       int       a=1, b=2; 
       double       c=3, d=4; 
        
       swap(&a, &b, sizeof a); 
       swap(&c, &d, sizeof c); 
       return 0; 
} 

위 코드를 어셈블한 리스팅은 아래와 같습니다.

    TITLE   D:\backup\test\pvs\pvs.c 
    .386P 
include listing.inc 
if @Version gt 510 
.model FLAT 
else 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
_DATA   SEGMENT DWORD USE32 PUBLIC 'DATA' 
_DATA   ENDS 
CONST   SEGMENT DWORD USE32 PUBLIC 'CONST' 
CONST   ENDS 
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS' 
_BSS    ENDS 
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS' 
_TLS    ENDS 
FLAT    GROUP _DATA, CONST, _BSS 
    ASSUME  CS: FLAT, DS: FLAT, SS: FLAT 
endif 
PUBLIC  _swap 
EXTRN   _free:NEAR 
EXTRN   _malloc:NEAR 
EXTRN   _memcpy:NEAR 
_TEXT   SEGMENT 
_pa$ = 8 
_pb$ = 12 
_size$ = 16 
_pm$ = -4 
_swap   PROC NEAR 

; 5    : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 51       push    ecx 

; 6    :    void *pm = malloc(size); 

  00004 8b 45 10     mov     eax, DWORD PTR _size$[ebp] 
  00007 50       push    eax 
  00008 e8 00 00 00 00   call    _malloc 
  0000d 83 c4 04     add     esp, 4 
  00010 89 45 fc     mov     DWORD PTR _pm$[ebp], eax 

; 7    :    
; 8    :    memcpy(pm, pa, size); 

  00013 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  00016 51       push    ecx 
  00017 8b 55 08     mov     edx, DWORD PTR _pa$[ebp] 
  0001a 52       push    edx 
  0001b 8b 45 fc     mov     eax, DWORD PTR _pm$[ebp] 
  0001e 50       push    eax 
  0001f e8 00 00 00 00   call    _memcpy 
  00024 83 c4 0c     add     esp, 12            ; 0000000cH 

; 9    :    memcpy(pa, pb, size); 

  00027 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  0002a 51       push    ecx 
  0002b 8b 55 0c     mov     edx, DWORD PTR _pb$[ebp] 
  0002e 52       push    edx 
  0002f 8b 45 08     mov     eax, DWORD PTR _pa$[ebp] 
  00032 50       push    eax 
  00033 e8 00 00 00 00   call    _memcpy 
  00038 83 c4 0c     add     esp, 12            ; 0000000cH 

; 10   :    memcpy(pb, pm, size); 

  0003b 8b 4d 10     mov     ecx, DWORD PTR _size$[ebp] 
  0003e 51       push    ecx 
  0003f 8b 55 fc     mov     edx, DWORD PTR _pm$[ebp] 
  00042 52       push    edx 
  00043 8b 45 0c     mov     eax, DWORD PTR _pb$[ebp] 
  00046 50       push    eax 
  00047 e8 00 00 00 00   call    _memcpy 
  0004c 83 c4 0c     add     esp, 12            ; 0000000cH 

; 11   :    
; 12   :    free(pm); 

  0004f 8b 4d fc     mov     ecx, DWORD PTR _pm$[ebp] 
  00052 51       push    ecx 
  00053 e8 00 00 00 00   call    _free 
  00058 83 c4 04     add     esp, 4 

; 13   : } 

  0005b 8b e5        mov     esp, ebp 
  0005d 5d       pop     ebp 
  0005e c3       ret     0 
_swap   ENDP 
_TEXT   ENDS 
PUBLIC  _main 
EXTRN   __fltused:NEAR 
_TEXT   SEGMENT 
_a$ = -4 
_b$ = -8 
_c$ = -16 
_d$ = -24 
_main   PROC NEAR 

; 16   : { 

  0005f 55       push    ebp 
  00060 8b ec        mov     ebp, esp 
  00062 83 ec 18     sub     esp, 24            ; 00000018H 

; 17   :    int a=1, b=2; 

  00065 c7 45 fc 01 00 
    00 00        mov     DWORD PTR _a$[ebp], 1 
  0006c c7 45 f8 02 00 
    00 00        mov     DWORD PTR _b$[ebp], 2 

; 18   :    double  c=3, d=4; 

  00073 c7 45 f0 00 00 
    00 00        mov     DWORD PTR _c$[ebp], 0 
  0007a c7 45 f4 00 00 
    08 40        mov     DWORD PTR _c$[ebp+4], 1074266112 ; 40080000H 
  00081 c7 45 e8 00 00 
    00 00        mov     DWORD PTR _d$[ebp], 0 
  00088 c7 45 ec 00 00 
    10 40        mov     DWORD PTR _d$[ebp+4], 1074790400 ; 40100000H 

; 19   :    
; 20   :    swap(&a, &b, sizeof a); 

  0008f 6a 04        push    4 
  00091 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00094 50       push    eax 
  00095 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00098 51       push    ecx 
  00099 e8 00 00 00 00   call    _swap 
  0009e 83 c4 0c     add     esp, 12            ; 0000000cH 

; 21   :    swap(&c, &d, sizeof c); 

  000a1 6a 08        push    8 
  000a3 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  000a6 52       push    edx 
  000a7 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  000aa 50       push    eax 
  000ab e8 00 00 00 00   call    _swap 
  000b0 83 c4 0c     add     esp, 12            ; 0000000cH 

; 22   :    return 0; 

  000b3 33 c0        xor     eax, eax 

; 23   : } 

  000b5 8b e5        mov     esp, ebp 
  000b7 5d       pop     ebp 
  000b8 c3       ret     0 
_main   ENDP 
_TEXT   ENDS 
END 

너무나 사실 그대로의 코드이기에 별로 살펴 볼 필요가 없습니다.

이번에는 템플릿 버전입니다.

template 
void swap(T &a, T &b) 
{ 
       T c; 
        
       c = a; 
       a = b; 
       b = c; 
} 

int main() 
{ 
       int       a=1, b=2; 
       double       c=3, d=4; 
        
       swap(a, b); 
       swap(c, d); 

       return 0; 
} 

마찬가지의 어셈블 리스팅입니다.

    TITLE   D:\backup\test\templ\templ.cpp 
    .386P 
include listing.inc 
if @Version gt 510 
.model FLAT 
else 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
_DATA   SEGMENT DWORD USE32 PUBLIC 'DATA' 
_DATA   ENDS 
CONST   SEGMENT DWORD USE32 PUBLIC 'CONST' 
CONST   ENDS 
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS' 
_BSS    ENDS 
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS' 
_TLS    ENDS 
;   COMDAT _IsEqualGUID 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT _== 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAH0@Z 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAN0@Z 
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE' 
_TEXT   ENDS 
FLAT    GROUP _DATA, CONST, _BSS 
    ASSUME  CS: FLAT, DS: FLAT, SS: FLAT 
endif 
PUBLIC  _main 
PUBLIC  ?swap@@YAXAAH0@Z                ; swap 
PUBLIC  ?swap@@YAXAAN0@Z                ; swap 
EXTRN   __fltused:NEAR 
_TEXT   SEGMENT 
_a$ = -4 
_b$ = -8 
_c$ = -16 
_d$ = -24 
_main   PROC NEAR 

; 19   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 83 ec 18     sub     esp, 24            ; 00000018H 

; 20   :    int a=1, b=2; 

  00006 c7 45 fc 01 00 
    00 00        mov     DWORD PTR _a$[ebp], 1 
  0000d c7 45 f8 02 00 
    00 00        mov     DWORD PTR _b$[ebp], 2 

; 21   :    double  c=3, d=4; 

  00014 c7 45 f0 00 00 
    00 00        mov     DWORD PTR _c$[ebp], 0 
  0001b c7 45 f4 00 00 
    08 40        mov     DWORD PTR _c$[ebp+4], 1074266112 ; 40080000H 
  00022 c7 45 e8 00 00 
    00 00        mov     DWORD PTR _d$[ebp], 0 
  00029 c7 45 ec 00 00 
    10 40        mov     DWORD PTR _d$[ebp+4], 1074790400 ; 40100000H 

; 22   :    
; 23   :    swap(a, b); 

  00030 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00033 50       push    eax 
  00034 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00037 51       push    ecx 
  00038 e8 00 00 00 00   call    ?swap@@YAXAAH0@Z   ; swap 
  0003d 83 c4 08     add     esp, 8 

; 24   :    swap(c, d); 

  00040 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  00043 52       push    edx 
  00044 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  00047 50       push    eax 
  00048 e8 00 00 00 00   call    ?swap@@YAXAAN0@Z   ; swap 
  0004d 83 c4 08     add     esp, 8 

; 25   : 
; 26   :    return 0; 

  00050 33 c0        xor     eax, eax 

; 27   : } 

  00052 8b e5        mov     esp, ebp 
  00054 5d       pop     ebp 
  00055 c3       ret     0 
_main   ENDP 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAH0@Z 
_TEXT   SEGMENT 
_a$ = 8 
_b$ = 12 
_c$ = -4 
?swap@@YAXAAH0@Z PROC NEAR              ; swap, COMDAT 

; 10   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 51       push    ecx 

; 11   :    T c; 
; 12   :    
; 13   :    c = a; 

  00004 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00007 8b 08        mov     ecx, DWORD PTR [eax] 
  00009 89 4d fc     mov     DWORD PTR _c$[ebp], ecx 

; 14   :    a = b; 

  0000c 8b 55 08     mov     edx, DWORD PTR _a$[ebp] 
  0000f 8b 45 0c     mov     eax, DWORD PTR _b$[ebp] 
  00012 8b 08        mov     ecx, DWORD PTR [eax] 
  00014 89 0a        mov     DWORD PTR [edx], ecx 

; 15   :    b = c; 

  00016 8b 55 0c     mov     edx, DWORD PTR _b$[ebp] 
  00019 8b 45 fc     mov     eax, DWORD PTR _c$[ebp] 
  0001c 89 02        mov     DWORD PTR [edx], eax 

; 16   : } 

  0001e 8b e5        mov     esp, ebp 
  00020 5d       pop     ebp 
  00021 c3       ret     0 
?swap@@YAXAAH0@Z ENDP                   ; swap 
_TEXT   ENDS 
;   COMDAT ?swap@@YAXAAN0@Z 
_TEXT   SEGMENT 
_a$ = 8 
_b$ = 12 
_c$ = -8 
?swap@@YAXAAN0@Z PROC NEAR              ; swap, COMDAT 

; 10   : { 

  00000 55       push    ebp 
  00001 8b ec        mov     ebp, esp 
  00003 83 ec 08     sub     esp, 8 

; 11   :    T c; 
; 12   :    
; 13   :    c = a; 

  00006 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00009 8b 08        mov     ecx, DWORD PTR [eax] 
  0000b 89 4d f8     mov     DWORD PTR _c$[ebp], ecx 
  0000e 8b 50 04     mov     edx, DWORD PTR [eax+4] 
  00011 89 55 fc     mov     DWORD PTR _c$[ebp+4], edx 

; 14   :    a = b; 

  00014 8b 45 08     mov     eax, DWORD PTR _a$[ebp] 
  00017 8b 4d 0c     mov     ecx, DWORD PTR _b$[ebp] 
  0001a 8b 11        mov     edx, DWORD PTR [ecx] 
  0001c 89 10        mov     DWORD PTR [eax], edx 
  0001e 8b 49 04     mov     ecx, DWORD PTR [ecx+4] 
  00021 89 48 04     mov     DWORD PTR [eax+4], ecx 

; 15   :    b = c; 

  00024 8b 55 0c     mov     edx, DWORD PTR _b$[ebp] 
  00027 8b 45 f8     mov     eax, DWORD PTR _c$[ebp] 
  0002a 89 02        mov     DWORD PTR [edx], eax 
  0002c 8b 4d fc     mov     ecx, DWORD PTR _c$[ebp+4] 
  0002f 89 4a 04     mov     DWORD PTR [edx+4], ecx 

; 16   : } 

  00032 8b e5        mov     esp, ebp 
  00034 5d       pop     ebp 
  00035 c3       ret     0 
?swap@@YAXAAN0@Z ENDP                   ; swap 
_TEXT   ENDS 
END 

이 리스트는 조금 살펴볼 부분이 있는데 아래 부분입니다.

; 23   :    swap(a, b); 

  00030 8d 45 f8     lea     eax, DWORD PTR _b$[ebp] 
  00033 50       push    eax 
  00034 8d 4d fc     lea     ecx, DWORD PTR _a$[ebp] 
  00037 51       push    ecx 
  00038 e8 00 00 00 00   call    ?swap@@YAXAAH0@Z   ; swap 
  0003d 83 c4 08     add     esp, 8 

; 24   :    swap(c, d); 

  00040 8d 55 e8     lea     edx, DWORD PTR _d$[ebp] 
  00043 52       push    edx 
  00044 8d 45 f0     lea     eax, DWORD PTR _c$[ebp] 
  00047 50       push    eax 
  00048 e8 00 00 00 00   call    ?swap@@YAXAAN0@Z   ; swap 
  0004d 83 c4 08     add     esp, 8 

보면 컴파일러는 ?swap@@YAXAAHO@Z라는 함수와 함께 ?swap@@YAXAANO@Z라는 함수를 호출 하는 것을 볼 수 있습니다. 실제로 컴파일 타임에 필요에 의해서 두가지 버전의 함수를 만든 것이죠. 전체 리스팅에서 아래로 가면 두가지 함수의 실제 코드가 따라 나오게 되는데, 스택 프레임 생성과 복사하는 크기 외에는 그렇게 큰 차이가 나지 않습니다.

여기서 우리는 결론을 내릴 수 있습니다. 템플릿의 구현은 PVOID와 전혀 상관이 없다. 단지 컴파일러가 컴파일 타임에 필요한 코드를 생성해 주는 것 뿐이다.

그럼 템플릿과 PVOID의 각기 장/단점을 알아봅시다.

PVOID로 만든 swap함수는 불행하게도 size가 실행시간에 결정되기 때문에, 힙을 사용할 수 밖에 없습니다. 물론 스택 할당 함수를 사용할 수도 있습니다. 하지만 함수 하나가 모든 자료형에 대해서 적용되므로 코드 크기는 작다고 할 수 있습니다.

템플릿 버전은 각각에 다른 버전의 함수가 만들어지기 때문에 크기가 커진다는 단점이 있습니다. 반면에 힙같은 외부 의존성이 낮기 때문에 빠르다고 할 수 있습니다.

결론을 정리하면 PVOID는 작고 느리다. 반면 템플릿은 크고 빠르다가 되겠습니다.

4. 무엇이 비교 대상인가?

그러나 사실 위의 3장은 전혀 관계없는 두가지의 것을 비교하고 있다. PVOID와 템플릿. 이건 템플릿의 구현 과정에 PVOID가 전혀 관여하지 않는다는 사실을 보여주기 위해서 설명한 장들이었습니다. 3번 장을 읽고는 의문이 들었을 것입니다. 템플릿이 크다고? 그렇다면 ATL/STL등에서 주장하는 작다는 말은 무엇인가?... 어떨때는 크다... 어떨때는 작다... 뭐야?????????????....

맞는 말입니다. 비교 대상에 따라 틀린 것이죠. 주어와 비교 대상이 빠졌기 때문에 오해가 생긴 것입니다.

템플릿은 PVOID에 비해서는 큰 코드가 만들어진다.
템플릿은 정적 라이브러리에 비해서는 작은 코드가 만들어진다.

이것이 결론입니다. 실제로도 템플릿과 PVOID를 통상적으로 비교하는 일은 없습니다. 정적 라이브러리가 가지는 단점과 비교할때 많이 사용됩니다. 정적 라이브러리는 불필요한 필요없는 코드들도 모두 링크되기 때문에 그만큼 오버헤드를 가지게 됩니다. 하지만 템플릿은 컴파일타임에 사용자가 호출한 함수들만 연결하기 때문에 필요없는 것들은 링크단계까지 아예 갈 일이 없는 것이죠. 실제로 그것이 ATL이 MFC에 비해서 가지는 장점이기도 합니다.

끝으로 C언어의 창시자인 커닝헌의 인터뷰 중 일부를 발췌해서 올립니다. 그는 C와 C++의 장,단점을 날카롭게 한가지로 일축해서 표현했습니다. 결국 도구는 다 그 용도에 맞게 써야된다는 것이죠...

Sometimes I do write C++ instead of C. C++ I think is basically too big a language, although there's a reason for almost everything that's in it. When I write a C program of any size, I probably will wind-up using 75, 80, 90% of the language features. In other words, most of the language is useful in almost any kind of program. By contrast, if I write in C++ I probably don't use even 10% of the language, and in fact the other 90% I don't think I understand. In that sense I would argue that C++ is too big, but C++ does give you may of the things that you need to write big programs: it does really make it possible for you to create objects, to protect the internal representation of information so that it presents a nice facade that you can't look behind. C++ has an enormous amount of mechanism that I think is very useful, and that C doesn't give you.

때때로 나는 C대신 C++을 사용해서 프로그램을 작성합니다. 제가 생각하기에 C++은 안에 모든 기능을 내장하기 때문이라고 하더라도 매우 큰 언어입니다. 제가 어떤 크기의 C프로그램이든 작성할때, 저는 75, 80, 90% 정도의 언어 기능을 사용합니다. 다시 말하면 어떤 종류의 프로그램이든 간에 대부분의 C언어 기능이 유용하다는 점 입니다. 이와는 대조적으로 제가 C++을 사용한다면 언어의 10% 정도도 사용하지 못합니다. 그리고 나머지 90%는 제가 이해하고 있다고 생각하지 않습니다. 이러한 점들에서 전 C++은 너무 크다고 말합니다. 그러나 C++은 큰 프로그램을 작성하는데 필요한 많은 기능들을 제공합니다. 객체를 만드는 것이 가능하며, 내부적인 정보의 표현을 보호할 수 있습니다. 그래서 결국 내부를 보지 못하게 만드는 훌륭한 외관을 표현할 수 있습니다. C++은 제가 생각하기에 아주 방대한 양의 유용한 메카니즘을 가지고 있습니다. 그리고 그것은 C언어가 당신에게 주지 못하는 점 입니다.

출처: MSDN Library
url: http://www.gosu.net/GosuWeb/ArticleMSView.aspx?ArticleCode=341

'프로그래밍 > C++' 카테고리의 다른 글

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
Posted by 마블(이환문)

해결 방법:

프로젝트 속성 - 구성 속성 - 일반 - 문자 집합

이 곳에서 유니코드 문자 집합 사용을 멀티바이트 문자 집합 사용으로 바꾸어 주면 된다

Posted by 마블(이환문)