반응형

BSTR IString::ToBSTR()
{
BSTR BStr;
int iWLength;

iWLength = ::MultiByteToWideChar(CP_ACP, 0, mcpStr, Length(), NULL, NULL);
BStr = ::SysAllocStringLen(NULL, iWLength);
::MultiByteToWideChar(CP_ACP, 0, mcpStr, Length(), BStr, iWLength);

return BStr;
}

반응형
반응형

PathFileExists(파일경로가 붙은 파일 이름) 파일이 존재하는지 여부

if(PathFileExists(PathName.ToChar()))

PathFileExists(파일경로가 붙은 파일 이름) 파일이 존재하는지 여부를 알려준다

반응형
반응형

http://avangs.info/zbxe/533483

3-5-나.확장열

문자 상수는 홑따옴표안에 문자를 써서 표기한다. 문자 Y에 대한 문자 상수는 'Y'다. 이 상수를 키보드로 입력하려면 키보드에서 '를 치고 Y를 치고 '를 치면 된다. 그런데 따옴표안에 직접 입력할 수 없는 문자가 있다. 대표적으로 개행 코드를 들 수 있는데 Enter키를 누르는 즉시 정말로 다음 줄로 내려가 버리기 때문에 따옴표안에 개행 코드를 담아서 표현하는 것은 불가능하다.

또한 문자 상수를 표현할 때 사용하는 홑 따옴표 구두점도 문자 상수로 바로 표현할 수 없다. ''' 이렇게 쓰면 두 번째 '가 닫는 따옴표인지 문자 '를 나타내는지 컴파일러가 구분할 수 없다. 그래서 키보드로 직접 입력할 수 없는 문자들은 좀 특수한 방법으로 표현하는데 이를 확장열(Escape Sequence)이라고 한다. 확장열은 백슬레쉬(\) 문자 다음에 기호 하나를 써서 표현한다.

확장열

코드

설명

\a

0x07

소리

\b

0x08

스페이스

\t

0x09

\n

0x0a

개행

\x##

0x##

16 코드

\###

0###

8 코드

\\

0x5c

백슬레쉬

\'

0x27

홑따옴표

\"

0x22

겹따옴표

\?

0x3f

물음표

개행 코드는 \n으로 표현하는데 이 코드는 First예제에서 이미 실습해 보았다. 탭이나 백 스페이스 같은 문자도 직접 키보드로 입력해서는 따옴표안에 표기할 수 없기 때문에 확장열로 표기해야 한다. 홑따옴표 문자 하나는 '''와 같이 표기할 수 없고 확장열을 사용해서 '\''와 같이 표기해야 한다.

확장열 표기에 \문자를 사용하기 때문에 \문자 자체도 확장열이 될 수밖에 없다. 16진 코드는 키보드에 없는 문자에 대해 코드를 직접 쓸 때 사용한다. 'A'는 '\x41'과 동일하다. 16진 코드를 확장열로 표기할 때 \다음의 x는 반드시 소문자로 써야 하며 대문자는 인정하지 않는다. 문자열 상수내에 16진 코드를 직접 쓸 경우 16진수로 인식되는 모든 문자를 확장열로 취급한다는 점을 주의하자. '\x53trike"는 \x53이 s이므로 "strike"이지만 "\-3econd"는 \다음의 53ec까지를 16진수로 해석해 버리므로 "second"가 되지 않고 에러로 처리된다. 왜냐하면 53ec는 문자 코드 범위 바깥이므로 하나의 문자가 아니기 때문이다. 이런 경우는 16진 코드를 쓰지 않거나 다른 방법으로 문자열을 표기해야 한다.

확장열을 쓰는 이유는 꼭 키보드로 표현하지 못해서뿐만 아니라 환경에 따라 달라질 수 있는 코드를 논리적으로 표현하기 위해서이다. 개행을 하는 방식은 시스템마다 다른데 윈도우즈에서는 CR, LF의 조합으로, 유닉스는 LF만으로, 매킨토시는 CR만으로 개행 문자를 표현한다. C는 이런 방식에 상관없이 개행을 표현할 수 있는 확장열을 제공하고 프로그래머는 개행이 필요할 때 \n이라고만 적으면 된다.

탭의 경우 일정한 자리를 띄우는 것이 아니라 현재 위치에서 다음 탭 위치로 이동하는 기능을 하므로 소스상에 입력된 탭에 의해 띄워진 빈칸과 실제 출력될 위치의 탭 크기가 다를 수 있다. 그래서 탭키를 문자열 상수에 직접 쓰지 않고 \t 확장열로 표기하여 출력되는 상황에 맞게 탭을 적용하도록 한다.


반응형
반응형

출처 : http://sungho0459.blog.me/40142019746






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

 

extern "C"를 자주 쓰긴 하지만 정확한 내용을 몰라 'jimbo73.egloos.com/1486292'으로 부터 퍼온 상태로 수정한 내용입니다.

컴파일러는 링커가 링킹작업시 오브젝트간 함수 이용 및 위치를 파악할 수 있도록, 컴파일시 사용된 함수에 관련한 정보(linkage)를 오브젝트 파일에 기록합니다.

 

 

linkage란 컴파일 시 함수이름 앞 또는 뒤에 '_'(언더바)등의 심볼을 덧붙이는 것을 말하는 것으로, C와 C++은 컴파일시 오브젝트 파일에 함수명, 변수명등에 심볼을 기록하는 방식이 다르다.

 

C에서는 함수의 이름이 유일하기 때문에 (Overloading 지원하지 않음) 함수 앞에 '_'등의 심볼만을 붙이면 된다. 하지만 C++에선 overloading을 지원하기 때문에 같은 이름의 함수를 여러개 가질 수 있다. 그래서 함수를 구분하기 위해 함수이름만으로 구분할 수 없게 되고, 인자의 개수와 데이터형에 대한 정보까지 가지게 되어 linkage 정보가 서로 다르게 된다.

 

그러므로 C와 C++을 혼합하여 사용 시, 두 언어간의 linkage방식이 틀려 함수 이용에 문제가 발생할 수 있다.

 

C와 C++을 혼합하는 프로그램에서는 link시 함수명을 각각의 방식으로 찾을 수 있도록 C언어 부분인지, C++ 부분인지를 확실히 명시해 주어야하며, 이때 linkage에 대한 지시자 역활을 하는 extern "C"를 사용한다.

 

사용방법 예제

#ifdef __cplusplus

  extern "C"{

#endif

 

    int func1();

    int func2();

 

#ifdef__cplusplus

  }

#endif

< #ifdef __cplusplus 이 문구는 C++이라면 이라는 컴파일러 지시자(조건문)입니다. >

 

 

 

 

 

p.s 간단히 정리해주신 jimbo73님에게 감사의 글을 올립니다.

[출처] extern "C"|작성자 에몬

반응형
반응형

[출처] : http://myhome.hanafos.com/~kukdas/doc/cpp/c_use-13.html


---------- 반올림 함수 1 -----------

#include <math.h> 
double round(double x)
/* 반올림을 해주는 함수,음수일 경우 반내림을 한다. */
{
   return ((x>0) ? floor(x+.5) : ceil(x-.5));
}

(설명)

C에는 특별히 반올림을 해주는 함수가 없다.
그러나 아주 간단하게 반올림을 할 수 있는 방법이 있다.
즉, x가 양수인 경우에 floor(x+0.5) 라고 해주면
반올림이 저절로 된다.
그러나 음수에서는 반내림(-1.9 => -2.0)을 하는 것이
오히려 상식적이므로 반올림을 하는 함수를 새로 제작.
ceil()은 올림을 하는 함수이고, floor()는 내림을 하는
함수이다.
단순히 return floor(x+.5) 또는
return (double)(int)(x+.5) 라고 하면 양수일 때만
원하는 결과를 가져온다.
반드시 math.h를 include 해주어야 한다.

---------- 반올림 함수 2 -----------

#include <math.h>

double round2(double a, int b)
/* 소수점 아래자리에서도 반올림 해주는 함수 */
/* a-반올림 할 수, b-반올림할 소수점 자리  */
{
   a*=pow10(b-1);
   a=(a>0) ?  floor(a+0.5) : ceil(a-0.5);
   a*=pow10(-(b-1));
   return a;
}

(설명)

앞서의 round() 함수는 소수점 아래자리에서는
반올림할 수가 없었다.
이것을 pow10() 함수를 써서 매개변수에 소수점 정보를
추가한 것이다.
그래서 b를 그 소수점 정보로 하여 원하는 위치로
10의 배수를 곱하여, 첫째자리에서 반올림을 한후
다시 원상복귀시킨 것이다.

   ex) p=round2(3.141592, 3); 
         => p에는 3.140000 이 저장된다.
 

반응형
반응형

[출처] 멤버 함수를 쓰레드 함수로 만들기.|

멤버 함수를 쓰레드 함수로 만들기.

오늘은 간단한 C++ 프로그래밍 기법에 대해 하나 써볼까 합니다.

간단한 기교를 부려볼 겸 클래스의 멤버함수를 쓰레드 함수로 작성하는 방법을 배워 보도록 하겠습니다.

(간단히 쓰레드 사용법도 배우고 일석이조! 야호! )

아직까진 그런 적은 없지만, 쓰레드를 돌릴 때 간간히 멤버함수를 쓰레드 함수로 제작하고플 때가 있더군요.

혹, 그런 분들을 위해 알려 드리겠습니다.

class TestClass {

INT B;

VOID TestFunction( INT A ) { B = A }; //이 함수를 쓰레드로 돌리고자 합니다.

VOID Start(); //이 함수에서 TestFunction을 호출하지요.

};

Start() 멤버 함수에서 TestFunction()을 쓰레드로 돌리고자 한다고 해보죠.

VOID TestClass ::Start() {

HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunction, NULL, NULL, NULL );

// ........ 무엇가의 작업을 하고.

WaitForSingleObject( hThread, INFINITE );

CloseHandle( hThread );

}

물론 안됩니다. 쓰레드로 돌아갈 함수는 정적으로 선언되어야 하며 정적 함수여야 하죠. 따라서,

class TestClass {

static VOID WINAPI TestFunction( INT A ); // 이제 원래 의도대로 이 함수를 정적 함수로 제작합니다.

VOID Start();

};

역시 물론 안됩니다. 직접 닥쳐보시면 알겠지만, 우리가 돌리고자 했던 TestFunction 함수에서는 B라는 비정적 멤버를 참조하고 있습니다.

결국은 다른 방법을 찾아야 합니다.

class TestClass {

VOID TestFunction( INT A ); //다시 원래대로 돌리고

static VOID WINAPI TestFunctionThread( LPVOID ); //대신 쓰레드가 돌아갈 정적 함수를 만듭니다.

VOID Start();

};

VOID Start() {

HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunctionThread, this, NULL, NULL );

// ........ 무엇가의 작업을 하고.

WaitForSingleObject( hThread, INFINITE );

CloseHandle( hThread );

}

VOID WINAPI TestFunctionThread( LPVOID p ) {

( (TestClass* ) p )->TestFunction( 3 );

}

자, 위 예를 통해 해결했습니다. 포인터. 즉 동적 호출을 통해서 정적 멤버 함수를 호출하였습니다.

TestFunction의 인자 값도 동적으로 주고 싶다면,

struct Arg {

TestClass* p;

INT A;

};

VOID Start() {

Arg arg = { this, 3 };

HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunctionThread, &arg, NULL, NULL );

// ........ 무엇가의 작업을 하고.

WaitForSingleObject( hThread, INFINITE );

CloseHandle( hThread );

}

VOID WINAPI TestFunctionThread( LPVOID p ) {

Arg* pArg = ( Arg* ) p;

pArg->p->TestFunction( p->A );

}

반응형
반응형

[Win API] 스레드 강좌 + CreateThread() 와 _beginthreadex() 함수의 차이 ★ 윈도우 Prog

2008/07/16 16:38

복사 http://blog.naver.com/kaltznnnyyy/140053695650




안녕하세요.

정말 오랫만의 포스팅입니다.

요새 이것저것 노느라 하느라 바빠서 공부와는 거리를 멀리하다가

다시 마음을 살짝 먹고, 글을 쓰고자 이렇게 달려왔습니다

(..라고 해봤자 계속 컴퓨터 앞입니다.)

이번에는 멀티스레드 관련해서 하나 이야기해보고 같이 실험을 해볼까합니다.

기본적으로 프로그램을 실행하게 되면 적어도 1개의 프로세스와 1개의 스레드를

생성하게 되는데, 그 1개의 스레드는 우리가 흔히 알고 있고 작성하고 있는

메인 스레드 ( Console일 경우 main() 함수로 호출 , Win32일 경우 WinMain() 함수로 호출 )

입니다. 메인 함수를 호출하게 되면 시스템 함수에 의해서 한개의 메인 스레드가 생성되는 것이죠.

멀티 스레드란 , 한 프로세스 내에서 메인스레드를 제외한 다른 스레드가 생성되고

돌아가고 있는 것을 말합니다. 한 프로세스 내에서 작업을 나눠서 할 수 있도록 도와주는

녀석이라고나 할까요. 그렇다고 절대 '동시'에 실행되는 것은 아닙니다. 엄청나게 빠르게

스레드 전환 이란 작업이 일어나고 있는 것 뿐이죠. 물론 프로세스도 동시에 실행되는 것이

아니라 프로세스간의 컨텍스트 스위칭 이란 작업이 빠르게 이루어지고 있는 것입니다. 이 작업이 프로그램을 동시에 돌아가도록 착각하게 만드는 것이죠.

멀티 스레드 관련이나 네트워크 관련이 아니고서는 대부분의 C, C++ 강좌 서적에는

이러한 부분에 대하여 자세하게 기술되어있지 않지요. ( 뭐 해봤자 머리만 아플테니... )

하지만 멀티스레드를 조금이라도 정확히 알고 싶으신 분이라면 한번쯤 이 글을 읽어보시는 것도

도움이 되리라 생각되는군요.

자 그러면 일단 스레드라는 녀석을 가볍게 살펴보도록 하겠습니다.

위 그림을 보시면 g_iData 라는 전역변수가 존재합니다.

저는 저 전역변수를 반복분을 통해서 증가시키면서 출력을 시키려고 합니다.

단, 멀티스레드를 사용하기로 하였으므로 증가를 시킬때 두 스레드로 나누어서

증가를 시켜보도록 하겠습니다.

( 뭐 멀티스레드의 장점을 설명하기에 적당한 예제는 아님을 미리 알려드립니다.^^

가장 이해하기 쉽도록 최대한 단순하게 만들었습니다.)

메인 스레드 이외에 다른 2개의 스레드를 생성하고 싶으면 CreateThread란 함수를 사용합니다.

일단 CreateThread 함수의 스펙을 살펴보도록 하죠.

첫 번째 인자는 보안 관련 인자인데, 특별한 처리가 아닌이상 NULL로 지정하시면 됩니다.

두번째는 스레드에 할당할 스택 사이즈인데 0을 인자로 넘기면 디폴트 사이즈가 지정됩니다.

중요한건 세번째 , 네번째 인자인데,

세번째 인자는 스레드 함수의 주소(포인터)를 넘겨줍니다. 함수의 주소는 그 함수의 이름과 같은 건 알고 계시다고 가정하겠습니다. 그런데 그 함수의 기본 모형은 아무렇게나 주어도 되는것인가?

절대 아닙니다. 이 스레드는 유저가 관리하는 영역이 아닌, 시스템이 관리하는 영역이기 때문에

정해진 함수의 원형에 맞게 작성을 해야 시스템이 올바르게 읽고 판단합니다.

넘겨야하는 함수의 원형은 아래와 같습니다.

DWORD WINAPI ThreadProc(LPVOID lpParameter);

인자는 LPVOID 타입인데 , LPVOID는 다시 풀어쓰면 Long Pointer void (void *) 형입니다.

그러므로 4byte 주소를 넘겨줄 수 있는 것이죠. 리턴으로 넘겨주는 DWORD 형은 종료코드이구요.

그러면 질문이 생길 수도 있습니다.

"헉! 거대한 프로그램에서 인자 하나 가지고 어떻게 여러개의 변수를 처리하나요?

스레드 이거 너무 비효율적 아닌가요?"

라고 말이죠. 하지만 조금만 생각을 달리하면 아주 쉽게 응용이 가능합니다. 나중에 아래에서 다시 설명을 드리겠습니다.

네번째 인자는 바로 위 스레드 함수에 넘겨야할 인자를 적어주시면 됩니다.

다섯번째 인자도 그냥 비워두시면 되구요 ( 무조건은 아니지만 지금 단계에서는 필요 없어요 )

여섯번째 인자에는 스레드의 ID 번호를 저장할 DWORD 자료형의 주소를 넘깁니다.

이 스레드 ID번호는 다른 프로세스간 통신 등을 할 때 필요합니다.

( 이 함수가 넘겨주는 HANDLE 은 동일 프로세스 내에서만 유효합니다. )

이 함수를 호출하게 되면 이 스레드를 제어할 수 있는 HANDLE을 리턴하게 되는데,

스레드가 제대로 생성되지 않았을 경우에는 NULL을 리턴하게 됩니다.

자, 말이 길어졌는데 그럼 위 설명을 토대로 간단한 멀티스레드 프로그램을 작성하도록 하겠습니다.

위와 같이 작성을 하게 되면 어떠한 결과가 나올까요? 예상해보시고 아래 답을 봐주세요.

생각과는 다르게 처참한 결과라는 생각이 드시는 분들도 있으실 겁니다.

까무잡잡한 화면에 아무것도 출력되지 않다니요!! 헝헝헝..ㅠ

이런 결과가 생기는 이유는 "메인 스레드는 추가로 생성된 스레드의 종료를 기다리지 않는다"는

것에 있습니다. 다시 말해 ThreadFunc 함수가 호출되어서 실행이 되려는 순간 메인 스레드에서는 할일이 전혀 없었으므로 바로 끝나게 되는데, 메인스레드가 끝나면 모든 프로그램을 종료가 되기 때문에

ThreadFunc 함수 내부의 구문은 실행되지 못한 채 종료가 되어버리는 것입니다.

얼래... 그러면 메인에서 추가된 스레드가 돌아가도록 한 1초정도만 기다리게 해보는건 어떨까요?

CreateThread 함수를 호출한 다음에 Sleep(1000); 을 적고 다시 실행시켜보도록 하죠.

그러면 아래와 같은 정답 비스무리한 결과(?!!)가 나오게 됩니다.

오오... 이제 제대로 돌아가는 군요.... 라고 안심하면 절!대! 안됩니다 ㅎㅎ

일단 첫번째 문제점은 호출한 스레드가 처리하는 동작이 몇초가 걸리는지 몇분이 걸리는지,

아니면 지속적으로 유지가 되는지 전~~혀 알 길이 없기때문입니다. 위 코드는

for문 30번 도는 일이 1초안에는 끝난다고 확신했기 때문에 가능했던 것이지요.

두번째 문제점은 중간에 Sleep으로 오랜시간 스레드를 잠재우면

도스 프로그램 같은 경우에는 해당이 안되지만 윈도우 환경에서 다수의 프로그램이

동시에 돌아갈 때, 다른 프로그램으로의 컨텍스트 스위칭이 이루어지지 않게되는 문제가

생겨버리는 것이지요.

흠...그렇다면 방법이 없는것인가?! ㅎㅎ 아닙니다. 아주아주 친절하게

적절한(김대기...죄송;ㅅ;) 함수를 제공합니다. 바로!!

위 함수의 첫번째 인자에 스레드의 핸들을 넘겨주고 , 두번째 인자에 대기 시간(밀리초)을 넘겨주면

그 스레드가 종료될 때까지 대기를 하게 됩니다. 그럼 이 함수를 적용한 코드를 보도록 하죠.^^

Sleep 함수 대신에 WaitForSingleObject() 함수를 호출합니다. 두번째 인자에 넘겨준

INFINITE는 0xffffffff 로 정의 되어있는 정의문으로 , 스레드가 끝날때까지 무조건 계~~속

기다린다는 의미입니다. ThreadFunc 함수가 종료코드를 반환하기 전까지는 그 다음줄을

실행하는 것을 절대 허락하지 않는것이죠.

자 이제 다시 돌려보겠습니다.

오호! 이것이 원하는 결과입니다! 뭔가 코드가 깔끔하네요.

자 그럼 스레드 끝!

.

.

.

이라고 하면 말도 안되겠죠 ㅋㄷ 이제 시작입니다.

지금까지는 g_iData 변수를 증가시키는 스레드가 1개였죠? 이제 그 작업을 2스레드가

나눠서 해보겠습니다.

위 소스코드를 아래와 같이 적당히 바꿔보지요-

어떤 결과가 나올 것 같나요?^ㅅ^

쉽게 예상하실 수 있겠죠??

그럼 예상해보시고 밑의 결과를 봐주세요~

뭔가 콰지직 쿵! 하는 느낌 안드세요?! 헉헉헉...

저..절대 원하는 결과가 아닙니다. (위 결과는 여러 상황에 따라서 바뀌게 됩니다.)

1부터 30까지 차례대로 출력해주었으면 했다구요!!!그렇죠?

위와 같은 결과는 두 스레드가 동시에 콘솔창에 출력을 행하려고 하기 때문에 일어나게 됩니다.

(위에서도 말했지만 엄밀히 말하면 동시는 아닙니다).

사실 문제는 이 뿐만이 아닙니다. 위 코드는 g_iData에 접근하고 증가시키는데 걸리는 시간이

아주 짧기 때문에 두 스레드간의 처리가 우연히 겹치지 않은 것 뿐이고,

컴퓨터가 아주 느리다면 30이 아닌 엉뚱한 결과가 나올 가능성이 충분히 있는 코드라는 점이죠.

이러한 문제를 해결하기 위해서는 스레드 간에 동기화를 해주어야 합니다.

멀티 스레드 프로그램을 작성하기 위해서는 반드시 반드시 꼭 꼭 알아야할 개념이죠.

(다시 부족전쟁 하고 나서 이어씁니다)


반응형
반응형

http://blog.naver.com/garins/90081899794




class 클래스명 (전방선언)


//------------------------
// Temp.h 파일
class CTemp
{
    //...
}
//------------------------
// CApp.h 파일

class CTemp;//전방 선언

class CApp
{
public :
//    CTemp m_Temp;    //전방선언 사용 불가능
//    CTemp* m_Temp;   //전방선언 사용 가능
}
//------------------------

 


전방선언 사용시 구현파일 (*.cpp)에 #include "*.h" (링크) 해주어야 한다.

최대한 링크 사용보다는 가능하면 전방선언을 사용할수 있다면 
실천하는 것이 빌드 시간을 단축하는 지름길이다.

 

인크루드로 인해서 소스가 엉키는 일을 획기적으로 줄일수 있는듯 하다..

전방선언으로 인해서 상속관계가 아니라면 굳이 .h에 인크루드를 사용할 필요가 없을듯

원본 : http://idrose1025.egloos.com/2201851

반응형
반응형

출처 : http://serious-code.net/moin.cgi/CppDebuggingTips#head-542083e07fc1db7784e0c88b48594020973726bd


스택 오버플로우 발생시 덤프 기록하기

    스택 오버플로우 발생시의 덤프 기록은 약간 다르게 처리를 해줘야한다. 어려운 건 아니고, 그냥 다른 스레드를 하나 생성해서 그쪽으로 스레드 핸들과 예외 포인터를 넘겨서 덤프를 기록하면 된다. 현재 스레드에서는 덤프 함수를 호출할 스택마저 모자랄 수 있기 때문이다. 하지만 다른 스레드를 하나 따로 생성하면 새로 생성한 스레드에서는 스택 공간이 충분하기 때문에 덤프를 무사히 기록할 수 있는 것이다. (스택은 스레드별로 유지되니까...)
    typedef struct _DUMP_PARAMETER
    {
        HANDLE              Thread;
        PEXCEPTION_POINTERS ExPtrs;
    } DUMP_PARAMETER, *PDUMP_PARAMETER;
    
    DWORD WINAPI WriteDump(LPVOID param)
    {
        PDUMP_PARAMETER dumpParam = reinterpret_cast<PDUMP_PARAMETER>(param);
        ...여기서 실제 덤프를 수행...
    }
    
    LONG WINAPI HandleException(PEXCEPTION_POINTERS exPtrs)
    {
        if (exPtrs)
        {
            PDUMP_PARAMETER dumpParam = (PDUMP_PARAMETER)malloc(sizeof(DUMP_PARAMETER));
            dumpParam->Thread = GetCurrentThread();
            dumpParam->ExPtrs = exPtrs;
            if (exPtrs->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
            {
                WriteDump(dumpParam);
            }
            else // 스택 오버플로 발생 시에는 별도의 스레드를 생성해서 덤프를 기록한다.
            {
                HANDLE hThread = CreateThread(NULL, 102400, WriteDump, dumpParam, 0, NULL);
                WaitForSingleObject(hThread, INFINITE);
                CloseHandle(hThread);
            }
        }
        return EXCEPTION_EXECUTE_HANDLER;
    }
    
    void main()
    {
        ...
        // 예외 처리 핸들러를 설정한다.
        SetUnhandledExceptionFilter(HandleException);
        ...
    }
     

    1 메모리 디버그 코드

      VisualCpp 메모리 코드

      의미
      0xCD, 0xCDCDCDCD 최초로 할당된 메모리
      0xFD, 0xFDFDFDFD 유저가 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
      0xDD, 0xDDDDDDDD 삭제된 메모리. _CRTDBG_DELAY_FREE_MEM_DF 플래그 값이다.
      0xCC, 0xCCCCCCCC 초기화되지 않은 지역 변수. /GX 옵션을 켜야 한다.
      0xABABABAB LocalAlloc 함수로 할당한 메모리 뒤에 붙는 바운드 체크용 메모리
      0xBAADF00D LocalAlloc 함수로 할당한 메모리
      0xFEEEFEEE HeapAlloc 함수를 위해 확보는 해놨으나, 아직 실제로 할당되지는 않은 메모리. 또는 HeapFree 함수를 통해 해제된 메모리
     

반응형
반응형


Introduction

클래스 템플릿이 다른 클래스 템플릿을 상속 할 경우, 무엇을 주의 하라는지 설명 하고 있다. 클래스 템플릿을 상속 하고 잘못 사용 했을 경우, "가상 함수"가 제 역활을 못할 수 있거나, 컴파일 자체가 되지 않을 수 있으므로, 이 부분은 꼭 봐야 할 것이다.

Content

시작하기에 앞서, 한가지 사실을 미리 알아두어야 한다. 바로 기반이 되는 클래스 템플릿이 파생 되는 클래스 템플릿에 기입 된 파라미터에 종속 될 경우와 종속되지 않을 경우, 이름을 찾는 규칙이 변화 한다는 사실이다.

이 두 경우 중 종속되지 않을 경우(기반 클래스 템플릿이 파생 클래스 템플릿에 기입된 템플릿 파라미터에)가 더 적은 규칙을 가지고 있으므로, 이를 먼저 정리하겠다.

1. 종속되지 않은 기반 클래스일 경우 주의 해야 할 점

종속되지 않은 기반 클래스일 경우, 일반 클래스를 상속 할 때와 거의 똑같다. 다른 점이 있다면, 한정된 이름이 파생 클래스 템플릿에 기입된 템플릿 파라미터 식별자(이름)와 종속되지 않은 기반 클래스에 있는 이름에 같이 있을 경우, 제일 먼저 종속 되지 않는 기반 클래스에 있는 이름을 먼저 찾으므로, 뜻하지 않게 컴파일이 안 될 수 있다.

template <typename X>
class Base
{
public:
int basefield;
typedef int T; // 이 부분이 중요.
};
template <typename T>
class D2 : public Base<double> // 종속되지 않은 기반 클래스
{
public:
void f()
{
basefield = 7;
}
T strage;
};
void g( D2<int*>& d2, int *p )
{
d2.strage = p; // 이 녀석이 컴파일 되지 않음
}
int main( void )
{
}

여기서 봐야 할 것은 d2.strage이 컴파일 되지 않는 것 이다. 왜냐하면 d2.strage는 int 형이기 때문이다. 아무리 템플릿 파라미터로 int* 를 기입하였어도 int로 기입되었다고 한다. 그 이유가 바로, 식별자 "T"를 평가 할 때, 템플릿 파라미터로 평가 된게 아니라 기반 클래스의 "typedef int T"로 평가 되었기 때문이다.

이는 종속되지 않은 기반 클래스에서 먼저 이름을 찾기 때문인 것을 증명한 셈이다. (이름을 중복되지 않게 짓는 습관을 가진 다면, 이러한 문제는 발생 되지 않을 것이다.)

그렇다면
2. 종속된 기반 클래스일 경우 주의 해야 할 점은 무엇인가?
종속되지 않은 기반 클래스일 경우, 그 기반 클래스 부터 이름을 찾는다고 했는데, 여기선 사용하는 식별자가 한정화 되지 않거나, 종속되지 않으면, 종속된 기반 클래스에서 이름 자체를 찾지 않는다. 이것이 다른 점이다.(하지만 MSVC 에선 찾는다. ... 표준하고 다르다..) 그 이유는 이름을 찾을 수 있게 될 경우, 템플릿 특수화로 이름의 상태가 변경(int형 변수에서 열거형 상수로)될 수 있기 때문이라고 한다.



template <typename X>
class Base
{
public:
int basefield;
};
template <typename T>
class DD : Base<T>
{
public:
void f()
{
basefield = 0;
}
};
template <>
class Base<bool>
{
public:
enum
{
basefield = 42, // basefield의 의미가 변했다.
};
};


이렇게 이름을 찾을 수 없게 함으로써 우회 할 순 있으나, 그 방법이 있는데, this를 사용하여 한정 시키는 방법과 한정자(::)를 사용하여 한정 시키는 방법, 종속시키는 방법, using을 사용 하는 방법들이 있다.

일반적으로 this를 사용 하는 형태가 더 좋다. 왜냐하면 한정자를 사용하는 방법은 자칫 가상함수 호출을 억제 할 수 있기 때문이다. 그러므로 가상 함수 호출에선 this를 사용하고, 선언같은 경우는 한정자를 사용 하면 된다.



template <typename T>
class B
{
public:
enum E
{
e1 = 6, e2 = 28, e3 = 496,
};
virtual void zero( E e = e1 );
virtual void one(E&);
};
template <typename T>
class D : public B<T>
{
public:
void f()
{
// this->E e 라고 선언 할수 없다!
// 이 경우가 바로 한정자 를 사용 해야만 할 때이다.
typename D<T>::E e;
// D<T>::zero() 라고 호출하면 가상 함수를 호출 할 수 없다.
// 그러므로 this를 사용 한다.
this->zero();
// e가 종속적인 데이터이므로 one은 자동적으로 종속적인
// 함수가 되므로, 기반 클래스 템플레서 찾을 수 있다.
one(e);
}
};

3. using을 사용 할 때 주의 해야 할 점

바로 다중 상속을 할 경우, 동일한 이름이 있을 때, 원하는 것을 정확하게 선택하는 것을 프로그래머의 몫으로 돌리기 때문이다. 인간이 한다는건 언제나 실수를 동반 할 수 있다는 것이기에 주의 해야 한다는 것이다.



template <typename T>
class Base
{
public:
int basefield;
};
template <typename T>
class Base2
{
public:
int basefield;
};
template <typename T>
class DD3 : public Base<T>, Base2<T>
{
public:
// 이렇게 using을 사용하여 가시화 시킨다.
using Base<T>::basefield;
//using Base2<T>::basefield;
void f()
{
basefield = 0;
}
};
int main( void )
{
DD3<int> d;
d.f();
}

Digression

가상함수 호출 때문에 꼭 this를 써야 하는건 분명 기억하고 넘어가야 한다. 이 부분은 g++ 에서 컴파일 할 수 있는 상황을 갖은 사람만이 해당 될려나? 안 배워서 나쁠껀 없지만...

2부 부분이 워낙 문법적인 이야기만 나오기 때문에, 지치는 경향이 있다. 하지만 3부와 4부의 내용을 흡수하기 위해선 꼭 필요한 내용이니 잊어먹어도 좋으니 익숙해 지는것을 포기해서는 안된다. 또 훗날 C++ Template Metaprogramming 을 해야 하므로, 너무 쳐지지 않도록 해야 한다.

이것으로 템플릿 이름 9장은 끝났다. 

반응형
반응형


다음




이전
다음






이전
다음





반응형
반응형

http://blog.daum.net/broodsc/2714333



오늘은 스택을 이용한 CALC 유틸리티(이하 CALC)에 관해서 설명(??)하겠습니다. ㅋ

CALC는 그냥 계산기 정도로 생각하시면 될듯...

CALC 유틸리티

수식을 중위표기법(Infix notation)으로 받아서 그걸 후위표기법(Postfix notation)으로 바꾸고...

이 후외 표기법의 수식을 스택을 이용하여 연산하여서 화면에 출력하게 하는 것...

이게 이 프로그램의 작동방식(??) 입니다. ;;;

여기서도 제한을 둡시다.

이 CALC는 간단한 정수와 사칙연산만 가능하게 한다는것.

그리고 입력시 스페이스를 넣으면 원하지 않으면 결과가 나올 수도 있다는 것.

뭐 다른 기능을 포함시키는것도 이 소스를 바탕으로 개선하시면 될듯 합니다.

방법 )

1. 수식의 표기법

보통 우리가 사용하는 수식은 뭘까요??

2 + 3 * (1 + 2) 이런 수식이죠??

이런걸 중위표기법(Infix notation)이라고 한답니다.

이유는?? 연산자가 두 피연산자 사이에 있어서... ;;;

1 + 2 를 보면 피연산자 1, 2 사이에 연산자 '+' 가 있죠?? ㅋ

그럼 연산자가 뒤에나 앞에 올 수도 있을까요?? 정답은 "그렇다" 입니다.

1 + 2 를 전위표기법(Prefix notation)으로 고치면 + 1 2 가 되겠고...

후위표기법(Postfix notation)으로 고치면 1 2 + ㅇㅋ??

그렇다면 왜 중위표기법을 놔두고 후위표기법을 이용하여 계산을 할려고 하는지??

그 이유가 궁금하지 않으신가요?? ;;;

그 이유는 중위표기법은 우리가 이해하기는 쉽지만 괄호를 만드시 써야하는 단점이 있다는 ;;;

위에보면 2 + 3 * (1 + 2) 이 식 있죠??

이 식에서 괄호를 제외한다면... 2 + 3 * 1 + 2 ...

두개의 수식이 서로 같나요?? 다르죠??

그럼 후위표기법은 괄호 없이도 가능하다는 말인가?? 정답은 "그렇다" 입니다. ㅋ

2. 중위표기법을 후위표기법으로 변환하는 방법 1

우선 뭄풀기(쫌 힘들지만 ;;) 단계로... 이해부터 하기 위해서...

한가지 제한을 두고 합시다.

뭐냐하면... 수식을 쓸때 무조건 괄호를 씌우는걸로...

예를들면 2+3*2 같은 경우에도 (2+(3*2)) 이런 식으로...

그럼 이걸 어떻게 후위표기법으로 변환할까요??

1. '(' 문자는 무시하고 넘어간다.

2. 피연산자는 그대로 출력한다.

3. 연산자는 스택에 푸시한다.

4. ')' 를 만나면 스택에서 팝하여 출력한다.

예를 들어 봅시다. (A + (B * C)) 라는 식...

'('는 무시한다고 그랬으니깐... 넘어가고 그 다음 'A'

'A'는 여기서 피연산자죠?? 그렇다면 출력. ( 출력 : A | 스택 : )

그 다음 '+' 이건 연산자... 그러니깐 푸시 ( 출력 : A | 스택 : + )

다음은 '(' ... 이건 무시.

그 다음 'B' 피연산자니깐 출력 ( 출력 : A B | 스택 : + )

다음은 '*' 연산자니깐 푸시 ( 출력 : A B | 스택 : * + )

그 다음 'C' 피연산자이므로 출력 ( 출력 : A B C | 스택 : * + )

그 다음 ')' ...

')' 만나면 스택에서 팝하여 출력이니깐... (출력 : A B C * | 스택 : + )

그 다음 또 ')'를 만났으니깐... 팝 하여 출력 (출력 : A B C * + | 스택 : )

그럼 (A + (B * C)) 의 후위표기법은 A B C * + 라는 결과가 나왔죠??

이제 이걸 가지고 C로 만들어 봅시다.

void postfix1(char *dst, char *src)

{

char c;

init_stack(); /* 스택을 초기화 */

while(*src)

{

if(*src==')') /* ')'를 만나면 팝하여 출력(dst에 저장) */

{

*dst++=pop();

*dst++=' '; /* 문자의 구분을 위해 */

src++;

}

else if(*src=='+' || *src=='-' || *src=='*' || *src=='/')

{ /* 연산자이면 스택에 푸시 */

push(*src);

src++;

}

else if(*src>='0' && *src<='9')

{ /* 숫자는 피연산자. 숫자를 그대로 복사 */

do

{

*dst++=*src++;

} while(*src>='0' && *src<=9');

*dst++=' ';

}

else /* 그 외에는 무시 */

src++;

}

*dst=NULL; /* 마지막에 NULL 문자 추가해서 문자열로... */

}

이건 쫌 쉽죠?? ;;;

3. 중위표기법을 후위표기법으로 변환하는 방법 2

위에 방법은 괄호가 꼭 있어야 했습니다.

그렇다면 이제 괄호가 없어도 우선순위에 맞춰서 변환하는걸 해 봐야겠죠??

이게 실제적으로 더 편리하고 유용한거니깐...

일단 우선순위를 고려해 줘야하니깐... 연산자별로 우선순위를 줍시다.

'(' 는 0, '+' 와 '-' 는 1, '*' 와 '/' 는 2 ...

1. '(' 를 만나면 스택에 푸시한다.

2. ')' 를 만나면 스택에서 '(' 가 나올 때까지 팝하여 출력하고 '(' 는 팝하여 버린다.

3. 연산자를 만나면 스택에서 그 연산자보다 낮은 우선순위의 연산자를 만날 때까지 팝하여 출력한 뒤에 자신을 푸시한다.

4. 피연산자는 그대로 출력한다.

5. 모든 입력이 끝나면 스택에 있는 연산자들을 모두 팝하여 출력한다.

후... 역시 이 책의 최대 약점이 또 다시 느껴지는군요...

직접 만들어볼 틈을 주기전에 이런 알고리즘을 제공하니깐... ;;;

보고 또 보고 해서 우리걸로 만들면 되겠죠?? 전 그렇게 할 수 있다고 스스로 믿을겁니다. ^^

예를 들어 봅시다.

(2 * (3 + (6 / 2) + 2) / 4 + 3 ... 차례대로... (그림 눌러서 보세요. 3자가 잘 안보이네요 ㅋ)

휴~

이걸 가지고 함수를 만들기 전에...!!

몇가지 간단한 함수부터 만들고 넘어갑시다. ^^

먼저...

int get_stack_top(void)

{

return (top < 0) ? -1 : stack[top];

}

이 함수는 스택의 최상단값을 리턴해주는 겁니다.

푸시될 연산자보다 높은 순위의 연산자가 스택의 상단에 있는지 없는지 보기 위해서...

그 다음 주어진 문자가 연산자인지 아닌지 판별하는 함수

int is_operator(int k)

{

return (k == '+' || k == '-' || k == '*' || k == '/');

}

두개 다 간단하죠??

그 다음... 연산자 우선순위를 수치로 바꿔주는 함수

int precedence(int op)

{

if (op == '(') return 0;

if (op == '+' || op == '-') return 1;

if (op == '*' || op == '/') return 2;

else

return 3;

}

흠... 이제 이 세 함수를 사용해서... 중위표기법 → 후위표기법 함수를 만들어 볼까요?? ;;;

void postfix(char *dst, char *src)

{

init_stack(); /* 스택 초기화 */

while (*src)

{

if (*src == '(') /* ( 를 만나면 푸시 */

{

push(*src);

src++;

}

else if (*src == ')') /* ) 를 만나면 ( 가 나올 때까지 팝 */

{

while (get_stack_top() != '(')

{

*dst++ = pop();

*dst++ = ' ';

}

pop();

src++;

}

else if (is_operator(*src)) /* 연산자이면 */

{

while (!is_stack_empty() &&

precedence(get_stack_top()) >= precedence(*src))

{ /* 우선순위가 높은 연산자들을 모두 팝 */

*dst++ = pop();

*dst++ = ' ';

}

push(*src); /* 그리고 푸시 */

src++;

}

else if (*src >= '0' && *src <= '9') /* 피연산자는 그냥 출력 */

{

do

{

*dst++ = *src++;

} while (*src >= '0' && *src <= '9');

*dst++ = ' ';

}

else

src++;

}

while (!is_stack_empty()) /* 모두 끝났으면 스택에 있는 모든 내용을 팝 */

{

*dst++ = pop();

*dst++ = ' ';

}

dst--;

*dst = 0;

}

4. 후위표기법 수식의 평가

후위 표기법 계산을 나타내는건 의외(??)로 쉽습니다.

1. 숫자를 만나면 스택에 푸시

2. 연산자를 만나면 두번 팝해서 그 두 데이터를 가지고 연산한 다음 다시 스택에 푸시

쉽... 죠?? 그러리라 믿습니다. ㅋ

예를들어 2 + 3 * 4 ... 이걸 후위표기법으로 나타내면 2 3 4 * +

2 3 4 는 숫자이므로 차례대로 푸시 ( 스택 : 4 3 2 )

그 다음 * ... '*' 는 연산자니깐 팝 두번하면 4 3 이거 두개를 * 연산해주면 12

12을 다시 푸시 ( 스택 : 12 2 )

그 다음 + ... '+' 는 연산자니깐 팝 두번하면 12 2 이거 두개를 + 연산하면 14 끝 !!

간단하죠??

단 한가지만 조심하면 됩니다. '-' 와 '/' 연산...

'+' 나 '*' 는 교환법칙이 성립하므로 팝 두번하여 그냥 계산하면 됩니다.

그러나 '-' 또는 '/' 는 교환법칙이 성립안합니다. 그러므로 팝을 따로 해주어야합니다.

그냥 pop() - pop() 를 하면 앞에 팝이 먼저 실행될지 뒤에 팝이 먼지 실행될지 모르기때문에...

그래서 팝을 한번 해서 저장해 둔 뒤에 그 값과 다음에 팝 하는 값을 계산해 주어야함.

그럼 이제 계산하는 함수를 만들어 볼까요??

int calc(char *p)

{

int i;

init_stack();

while (*p)

{

if (*p >= '0' && *p <= '9')

{

i = 0;

do

{

i = i * 10 + *p - '0';

p++;

} while (*p >= '0' && *p <= '9');

push(i);

}

else if (*p == '+')

{

push(pop() + pop());

p++;

}

else if (*p == '*')

{

push(pop() * pop());

p++;

}

else if (*p == '-')

{

i = pop();

push(pop() - i);

p++;

}

else if (*p == '/')

{

i = pop();

push(pop() / i);

p++;

}

else

p++;

}

return pop();

}

한가지 눈여겨 볼것 더... 바로 숫자열을 정수로 변환하는 것...

atoi() 라는 아시는 분이라면 쉽게 이해하실텐데...

int atoi(char *s)

{

int i=0;

while(*s>='0' && *s<='9')

i = i * 10 + *s++ - '0';

return i;

}

이게 바로 atoi() 함수입니다. 보시고 쪼끔만 생각하시면 이해하실듯...

결과 )

실행은

"CALC(파일이름) 수식" 하면 됩니다.

단, 스페이스를 사이에 집어 넣으면 원치 않은 결과가 나오니깐...

(공백이 있다면 argc 3으로 컴퓨터가 이해하니깐 ;;;) 조심~

반응형
반응형

저자 : 최흥배 마이크로소프트 Visual C++ MVP

0. 서두 5
0.1 C++의 문제점 5
0.2 C++는 시대에 뒤 떨어진 프로그래밍 언어일까요? 6
1. C++0x에 대해서 7
1.1 C++의 새로운 표준 C++0x 7
1.2 Visual C++의 이전 버전과의 호환성 7
1.3 Visual C++ 9와 Visual C++ 10 8
2. auto 9
2.1 정적 언어와 동적 언어의 차이 9
2.2 C#의 var 9
2.3 C++에서 STL(표준 템플릿 라이브러리)를 사용할 때 불편한 점 10
2.4 컴파일 타임 때 타입을 정하는 'auto' 10
2.5 auto를 사용한 예제 11
2.5.1 지역 변수로 사용 11
2.5.2 클래스 정의에 사용 12
2.5.3 STL에서 사용 12
2.6 핵심 요약 14
3. static_assert 15
3.1 assert와 #error 15
3.2 assert와 #error를 사용할 수 없을 때 15
3.3 static_assert의 형식 15
3.4 static_assert 사용 예 16
3.4.1 프리프로세스 지시어 대체 16
3.4.2 템플릿에서 사용 16
3.4.3 변수의 크기 조사 17
3.5 핵심 요약 17
4. 우측 값 참조 (RValue Reference) 18
4.1 C++98/03에서의 실수 18
4.2 좌측 값(LValue)과 우측 값(RValue) 19
Contents
Contents
4.3 좌측 값 참조와 우측 값 참조 20
4.4 Move semantics 21
4.5 Move 생성자와 Move 대입 연산자 22
4.6 Move semantics에 의한 성능 향상 23
4.7 move 생성자와 move 대입 연산자 사용 예 24
4.8 std::move 25
4.9 VC 10에서의 STL과 우측 값 참조 26
4.10 우측 값 참조를 사용할 때 주의할 점 28
4.11 우측 값 참조와 좌측 값 참조의 함수 오버로드 29
4.12 우측 값 참조는 우측 값이 아니다 30
4.13 Perfect Forwarding 30
4.14 핵심 요약 34
5. 람다 (lambda) 35
5.1 C#과 람다 35
5.2 C++에서 STL 알고리즘을 사용할 때 불편했던 점 36
5.3 C++에서의 람다 사용 법 37
5.3.1 문법 37
5.3.2 람다를 변수에 대입 37
5.3.3 람다를 함수의 인자로 사용하기 38
5.3.4 람다의 파라미터 38
5.3.5 반환 값 넘기기 39
5.4 캡쳐 39
5.5 클래스에서 람다 사용 43
5.6 STL의 find_if에서 람다 사용 44
5.7 람다 식을 STL 컨테이너에 저장 45
5.8 람다를 반환하는 함수 46
5.9 람다에서의 재귀 46
5.10 핵심 요약 47
6. decltype 48
6.1 템플릿 프로그래밍 시 문제 48
6.2 decltype 사용하기 48
7. nullptr 49
7.1 nullptr이 필요한 이유 49
7.2 nullptr 구현 안 50
7.3 nullptr 사용 방법 50
7.4 nullptr의 올바른 사용과 틀린 사용 예 50
8. 참고 52
0. 서두
Visual C++ 10과 C++0x 0. 서두
5
0.1 C++의 문제점
C++가 처음 나왔을 때는 다양한 하드웨어로의 이식성이 높고, 하드웨어를 잘 활용하여 고성능 프로그
램을 만들 수 있으며 객체지향 프로그래밍을 할 수 있는 구조와 표준 템플릿 라이브러리(STL) 덕택에
C 언어 보다 더 좋은 생산성을 얻을 수 있어서 단숨에 C 언어를 제치고 프로그래머에게 가장 인기 있
는 프로그래밍 언어가 되었습니다. 그러나 지금은 다른 언어들에게 최고의 자리를 내어 주었습니다.
C++ 언어가 나왔을 때는 하드웨어 성능이 지금보다 빈약한 시기로 프로그램이 작은 메모리와 낮은
CPU 점유율을 가지도록 성능에 대해 지금보다 더 많이 신경을 쓰면서 프로그램을 만들었지만, 지금은
멀티 코어 CPU와 기가 바이트 단위의 메모리를 가진 컴퓨터가 일반화된 시대로 바뀌어 생산성을 더
중요시 하고 있습니다.
C++가 예전만큼의 인기를 얻지 못하는 것은 근래에 나온 언어에 비해서 사용하기가 쉽지 않고 생산성
이 떨어지기 때문이라고 생각합니다.
“왜 C++이 어려울까요?”라고 트위터에 질문을 올려보았습니다. 이 질문에
@whatthepaul님은
“사용자가 알아야 할 것이 너무 많아서가 아닐까요? 그만큼 속도에서는 이득이 있는 거겠지만
생산성은 떨어지겠죠. 점점 생산성이 중요해지는 추세인데 좀 반대에 있는 느낌이죠 -_-;”,
@birdkr님은
“문법이나 포인터 개념도 어렵지만, 저는 지원해주는 기본 라이브러리가 턱없이 부족한 것도 한
원인이라 생각합니다. 요즘 인기 있는 언어들처럼 뭔가 뚝딱뚝딱하면 그럴 듯 한 것이 나와야 하
는데 C++은 간단한 것도 쉽게 만들 수 없으니까요.”
라는 의견을 주셨습니다. 두 분의 의견은 제 주위의 C++ 개발자들이나 인터넷의 개발자 커뮤니티나 블
로그 등에 있는 C++에 대한 불만과 비슷합니다.
처음 C++이 만들어질 때에 비해서 지금은 세상도 많이 변했고, 컴퓨터 하드웨어 성능도 훨씬 더 좋아
졌습니다. 이젠 C++도 변화해야 할 때입니다. 그리고 다른 프로그래밍 언어들처럼 생산성을 향상 시켜
야 합니다.
Visual C++ 10과 C++0x 0. 서두
6
0.2 C++는 시대에 뒤 떨어진 프로그래밍 언어일까요?
요즘은 컴퓨터 하드웨어 성능이 예전에 비해서 훨씬 좋아져서 프로그램을 만들 때 성능보다는 높은 품질로
빨리 만드는 것이 중요하다고 했습니다. 그러나 정말 성능을 신경 쓰지 않아도 될까요?
어떤 프로그램이냐에 따라서 답변이 틀릴 것이라고 생각합니다. 예전에 비하면 성능에 크게 신경 쓰지 않아
도 괜찮은 분야가 더 늘었지만 아직도 몇몇 분야에서는 성능에 신경을 써야 합니다. 제가 알고 있는 분야 중
임베디드와 게임은 아직도 성능에 신경을 써야 하는 분야합니다.
특히 제가 일하고 있는 게임 업계는 언제나 하드웨어의 발전 속도보다 게이머들이 이전보다 더 좋은 고화질
의 그래픽, 더 사람 같은 AI를 원하고 있기 때문에 프로그래머들은 언제나 성능에 많은 신경을 써야 합니다.
또 HD급의 그래픽을 사용하는 게임이 아니라면 낮은 사양의 CPU와 작은 메모리를 가진 하드웨어에서도 게
임을 실행되게 하기 위해 최적화에 많은 시간을 들입니다.
아직은 C#이나 Java로는 C++과 같은 높은 성능을 필요로 하는 게임을 만들기가 어렵기 때문에 주류급 게
임을 만들 때는 사용할 수 없습니다.
아무리 하드웨어 사양이 높아지고 있더라도 하드웨어를 최대한 활용하여 높은 성능을 필요로 하는 프로그램
을 만들어야 하는 분야는 여전히 있습니다. 이런 곳에서는 C#이나 Java로는 턱 없이 부족합니다. C++가 적
격입니다.
C++의 필요성은 충분히 알고 있지만 C++의 학습의 어려움, 낮은 생산성 등 부족한 부분을 빨리 누군가 메
꾸워 주기를 바라는 분들이 많이 있으리라 생각합니다. 바로 이런 부족한 부분을 메꾸기 위해서 C++의 새
로운 표준이 현재 만들어지고 있습니다. 그리고 기쁜 소식은 표준 작업이 끝나기 이전에 새로운 C++의 기
능을 새로 나올 Visual C++을 통해서 사용할 수 있습니다
Visual C++ 10과 C++0x 1. C++0x에 대해서
7
1.1 C++의 새로운 표준 C++0x
C++은 처음 표준을 만들 때 다양 사람들의 의견을 반영하여 완성도 있게 만들어졌고, 이미 C++로 만
들어진 프로그램이 많이 있어서 C#이나 Java처럼 새로운 표준이 자주 만들어지지 않아서 어떤 분들은
C++은 이제 과거의 언어로 더 이상 발전하지 않는 언어라고 생각하시는 분도 있으리라 생각합니다.
C++은 결코 과거에 머물러 있는 언어가 아닙니다. 그 증거로 몇 년전부터 새로운 C++의 새로운 표준
이 만들어지고 있습니다.
새로운 표준이 될 개정안을 임시적으로 C++0x라고 부르고 있습니다(C++0x 이전에는 C++98과
C++03 라고 부르는 표준안이 있었습니다).
표준 위원회는 2009년 안에 표준을 확정하기 위해서 2006년까지 받았던 제안을 중심으로 표준 작업
을 하고 있다고 합니다. 하지만 C++0x라고 가칭을 붙이면서까지 2009년 안에 표준 작업을 끝내려고
했지만 결과적으로 2010년을 넘겼습니다. 2009년을 넘겼으므로 C++0x라는 가칭의 이름도 바뀌어야
하지만 가칭을 바꾸면 혼란이 생길 수 있으므로 C++0x라는 가칭을 그대로 사용하기로 했습니다.
C++0x는 코어 언어의 기능 추가와 표준 C++ 라이브러리(STL)를 확장이라는 크게 두 개의 분류로 나
누어서 작업을 하고 있습니다.
1.2 Visual C++의 이전 버전과의 호환성
C++의 창시자이자 C++ 표준 위원회의 멤버인 Bjarne Stroustrup는 새로운 C++ 표준은 이전의 표준
과 100% 호환성을 가진다고 합니다. 즉 이전에 만들었던 C++ 코드들이 표준을 준수했다면 새로운 표
준에서 컴파일 하는데 아무런 문제가 없습니다.
예전에 Visual C++ 6(이하 VC6)에서 Visual C++ 7(이하 VC 7)로 넘어갈 때 VC 6에서 만들었던
코드가 VC 7에서는 에러와 경고를 내서 순조롭게 넘어가지 못한 경우가 있었습니다. 지금도 이것 때문에
아직도 VC 6을 사용하고 있는 곳도 있는 것으로 알고 있습니다.
VC 6에서 만들었던 코드가 VC 7에서 문제가 된 이유는 VC 6는 C++98 표준이 확립되기 전에 나와서
VC 6에서 만들었던 코드 중에는 표준에 맞지 않는 코드가 있기 때문입니다. 그러나 VC 7은 표준을
거의 완벽하게 준수하고 있어서 VC 6에서 만들었던 코드 중 표준에 맞지 않는 코드는 에러나 경고를
발생시킵니다.
VC 7부터는 표준을 거의 100% 준수하고 있으므로(참고로 100% C++ 표준을 준수한 상업용 컴파일
러는 거의 없습니다) VC 7에서부터 만들었던 코드는 Visual C++ 10(이하 VC 10)에도 문제 없이 컴
파일 할 수 있습니다.
즉 VC 7, VC 8, VC 9에서 컴파일에 문제가 없는 코드라면 VC 10에서도 수정 없이 그대로 사용할
수 있습니다.
1. C++0x에 대해서
Visual C++ 10과 C++0x 1. C++0x에 대해서
8
1.3 Visual C++ 9와 Visual C++ 10
C++ 표준위원회에서 C++0x에서 정책적으로 라이브러리 부분을 언어적인 부분보다 우선 시 하자고
정했기 때문에 tr1이라는 라이브러리가 VC 9가 나올 무렵에 나와서 VC 9가 출시 된 이후 Visual
Studio Service Pack을 통해서 VC 9에서 사용할 수 있게 되었습니다.
VC 10에서는 VC 9에 추가된 tr1과 달리 언어적인 부분에서의 새로운 기능이 구현 되었습니다.
언어적인 부분의 개선에 의해서 C++ 언어 표현력이 증대되었고 성능적 측면에서도 개선이 이루어졌습
니다.
VC 10에 구현된 C++0x의 새로운 기능은 VC 9의 tr1과 달리 언어적 측면에서의 새로운 기능이라서
새롭게 바뀐 C++을 이전에 비해 훨씬 더 강하게 체감할 수 있습니다.
VC 10에 구현된 C++0x의 새로운 기능은 auto, static_assert, RValue Reference, lambda,
decltype, nullptr입니다.
다음 장부터는 새로운 기능들에 대해서 설명할 테니 이전 보다 더 편리하고 강력해진 C++의 새로운
기능을 직접 느껴보시기 바랍니다. 보통의 C++ 프로그래머가 아닌 새로운 C++ 표준을 사용하는
C++0x 프로그래머가 되시기 바랍니다.
Visual C++ 10과 C++0x 2. auto
9
2.1 정적 언어와 동적 언어의 차이
근래에 Ruby나 Python과 같은 스크립트 언어가 많은 인기를 얻고 있습니다. Ruby나 Python 같은
언어를‘동적 언어’라고 합니다. 반대로 제가 주로 사용하는 언어인 C++는‘정적 언어’라고 합니다.
정적 언어와 동적 언어는 여러가지 차이점이 있습니다. 그중 정적 언어는 변수 type을 선언이나 정의
할 때 명시적으로 지정해야 합니다. 그러나 동적 언어인 Ruby, Python은 변수 type을 명시적으로 지
정할 필요가 없어서 편리합니다.
< 코드 2-1. C/C++에서의 지역 변수 정의 >
<코드>
void BuyItem()
{
int Money = 500;
........
}
</코드>
< 코드 2-2. Ruby에서의 지역 변수 정의 >
<코드>
def BuyItem
Money = 500;
......
end
</코드>
2.2 C#의 var
C#은 C++과 같은 정적 언어이지만 var라는 키워드를 사용하여 변수 type을 명시적으로 지정하지 않
아도 됩니다. 동적 언어와 차이점은 변수의 type을 실행할 때가 아닌 컴파일 할 때 결정합니다.
C#의 var 키워드를 사용하면 코딩 시 번거롭거나 코드의 가독성을 나쁘게 하는 코드를 없애 수 있어서
아주 유용합니다. var는 특히 C#에서 LINQ를 사용할 때 자주 사용합니다.
< 코드 2-3. LINQ에서 var를 사용할 때와 사용하지 않을 때 비교>
<코드>
// LINQ에서 var를 사용하지 않는 경우
IEnumerable<IGrouping<string, Student>> studentQuery3 =
from student in students group student by student.Last;
2. auto
Visual C++ 10과 C++0x 2. auto
10
// LINQ에서 var를 사용한 경우
var studentQuery3 = from student in students group student by student.Last;
</코드>
<코드 2-3>에서 var 키워드를 사용하여 LINQ를 사용하면 코딩 해야 할 량도 줄어들고 코드 가독성도
var를 사용하지 않을 때에 비해 훨씬 더 좋아진 것을 알 수 있습니다.
2.3 C++에서 STL(표준 템플릿 라이브러리)를 사용할 때 불편한 점
C++에서 STL을 사용할 때 코딩이 불편할 때가 있습니다. 예를 들면 list와 같은 STL 컨테이너에서 컨
테이너에 있는 모든 요소를 순회할 때입니다.
< 코드 2-4. C++에서 list 사용 >
<코드>
list<int> NumList;
NumList.push_back( 10 );
for( list<int>::iterator iter = NumList.begin();
iter != NumList.end();
++iter )
{
……
}
</코드>
list 컨테이너의 첫 번째 요소를 얻기 위해서 반복자를 정의할 때 꽤 길게 코딩 해야 됩니다(map과 같
은 연관 컨테이너는 훨씬 더 깁니다). 위의 경우는 그나마 좀 짧은 편이고 템플릿을 사용한 클래스를
list의 type으로 사용하는 경우는 아주 길어집니다.
<코드 2-4>를 보면 앞선 <코드 2-3>에서 var 키워드를 사용하지 않을 때와 비슷한 불편함이 있다는
것을 알 수 있습니다. 만약 C++에도 C#과 같이 var 라는 것이 있으면 이 문제를 쉽게 해결할 수 있겠죠?
C++0x에서는 바로 var와 같은 것을 만들어서 <코드 2-4>과 같은 불편함을 해결 하였습니다.
해결 방법은 C++0x에서 새로 생긴‘auto’라는 키워드를 사용하는 것입니다.
2.4 컴파일 타임 때 타입을 정하는 'auto'
auto를 사용하면 변수를 정의할 때 명시적으로 type을 지정하지 않아도 됩니다. auto로 정의한 변수를
초기화할 때 type을 결정합니다. 즉 C#의 var와 같이 컴파일 타임 때 변수의 type을 결정합니다. 하지
만 클래스의 멤버 변수나 전역변수, 함수의 인자로는 auto를 사용할 수는 없습니다.
auto가 복잡한 개념은 아니지만 변수를 정의할 때는 언제나 type을 지정해야 한다고 아주 당연하게 생
각하는 C++ 프로그래머로서는 조금 과장을 해서 auto는 아주 획기적이라고 느끼시는 분들도 있으리라
생각합니다(저는 C++0x를 공부하기 전에는 C++에서는 절대 auto 같은 기능은 지원하지 않고 이런
것은 C#과 같은 근래의 언어나 스크립트 언어에서만 가능한 것이라고 생각하고 있었습니다).
Visual C++ 10과 C++0x 2. auto
11
auto 키워드는 C#의 var와 비슷하고, 개념이나 사용 방법이 아주 간단합니다. ‘변수를 정의할 때 명시
적으로 type을 지정하지 않고 컴파일 타임 때 결정’하게 해주는 키워드라고 기억하시면 됩니다.
그럼 auto를 어떻게 사용하는지 예제 코드를 보겠습니다.
2.5 auto를 사용한 예제
2.5.1 지역 변수로 사용
문자열과 정수를 담을 변수를 auto를 사용하여 정의하면 아래와 같습니다.
< 코드 2-5. 문자열과 정수 변수에 auto 사용 >
<코드>
#include <iostream>
using namespace std;
int main()
{
// char*
auto NPCName = "BugKing";
cout << NPCName << endl;
// int
auto Number = 1;
cout << Number << endl;
getchar();
return 0;
}
</코드>
< 결과 2-5 >
또 당연히 포인터나 참조, const도 사용할 수 있습니다.
< 코드 2-6. 포인터, 참조, const에 사용 >
<코드>
#include <iostream>
using namespace std;
int main()
{
int UserMode = 4;
auto* pUserMode = &UserMode;
cout << "pUserMode : Value - " << *pUserMode << ", address : " << pUserMode <<
endl;
Visual C++ 10과 C++0x 2. auto
12
auto& refUserMode = UserMode;
refUserMode = 5;
cout << "UserMode : Value - " << UserMode << " | refUserMode : Value - " <<
refUserMode << endl;
getchar();
return 0;
}
</코드>
< 결과 2-6 >
2.5.2 클래스 정의에 사용
클래스를 정의할 때도 사용할 수 있습니다.
< 코드 2-7. 클래스 생성 때 auto 사용 >
<코드>
struct CharacterInvenInfo
{
int SlotNum;
int ItemCode;
};
........
auto* CharInven = new CharacterInvenInfo();
........
</코드>
2.5.3 STL에서 사용
auto 키워드는 템플릿 프로그래밍이나 STL을 사용할 때 진가를 더 발휘합니다.
auto가 없을 때는 STL의 컨테이너를 사용할 때 반복자를 정의 하기 위해 길게 코딩을 하던가 또는
typedef를 사용하였지만 auto가 생겨서 이런 불편함이 없어졌습니다.
<코드>
typedef std::list<MCommand*> LIST_COMMAND;
LIST_COMMAND::iterator iter = m_listCommand.begin();
</코드>
를 아래처럼 바꿀 수 있습니다.
<코드>
Visual C++ 10과 C++0x 2. auto
13
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
auto iter = m_listCommand.begin();
</코드>
아래의 예제를 보면 auto를 사용하여 STL 사용이 얼마나 간단해졌는지 쉽게 알 수 있습니다.
< 코드 2-8. vector의 요소를 순회할 때 auto 사용 >
<코드>
#include <iostream>
#include <vector>
using namespace std;
struct Item
{
int ItemCode;
int Money;
int SkillCode;
};
int main()
{
cout << "Use vector Iterator - 1" << endl;
vector< int > ItemCodeList;
ItemCodeList.push_back( 20 );
ItemCodeList.push_back( 30 );
ItemCodeList.push_back( 40 );
for( auto IterPos = ItemCodeList.begin(); IterPos != ItemCodeList.end(); ++IterPos)
{
cout << "ItemCode : " << *IterPos << endl;
}
cout << endl;
cout << "Use vector Iterator - 2" << endl;
vector< Item > ItemList;
Item item1; item1.ItemCode = 1; item1.Money = 100; item1.SkillCode = 0;
Item item2; item2.ItemCode = 2; item2.Money = 200; item2.SkillCode = 10;
Item item3; item3.ItemCode = 3; item3.Money = 300; item3.SkillCode = 0;
ItemList.push_back( item1 );
ItemList.push_back( item2 );
ItemList.push_back( item3 );
for( auto IterPos = ItemList.begin(); IterPos != ItemList.end(); ++IterPos)
{
cout << "ItemCode : " << IterPos->ItemCode << ", Money : " << IterPos-
>Money << endl;
}
cout << endl;
getchar();
return 0;
}
</코드>
Visual C++ 10과 C++0x 2. auto
14
< 결과 2-8 >
auto는 정말 단순한 개념과 사용 방법이면서 바로 C++ 프로그래머의 작업에 많은 도움을 주는 기능입
니다. 아마 모든 C++ 프로그래머들이 많이 애용하는 기능 중의 하나가 될 것 이라고 생각합니다.
2.6 핵심 요약
auto를 사용하면
1. 지역 변수를 정의 때 명시적으로 type을 지정하지 않아도 됨
2. 컴파일 타임 때 type을 결정
3. 코딩이 간편해지고, 코드 가독성이 좋아짐
Visual C++ 10과 C++0x 3. static_assert
15
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
3.1 assert와 #error
C++로 프로그래밍할 때 버그나 에러가 발생할 위험이 있는 곳에 경고를 발생시켜 문제를 빨리 발견하
기 위해서 assert 매크로나 #error 프리프로세서 지시어를 사용합니다. (프리프로세스는 #ifdef 등을
사용할 때를 말합니다).
assert와 #error 중 보통 assert를 자주 사용하며 assert는 논리적인 오류 찾기, 작업 결과 확인, 처리
해야 할 오류 조건 테스트를 할 때 사용합니다.
assert는 프로그램이 실행 될 때 평가 됩니다(디버그 모드에서 사용합니다).
< 코드 3-1. assert 사용 예 >
</코드>
PLAYER* pPlayer;
……
assert( NULL != pPlayer )
</코드>
3.2 assert와 #error를 사용할 수 없을 때
assert는 실행 시에 사용하고, #error는 프리프로세스에 사용하기 때문에 템플릿 실체화 시(컴파일 타
임)에는 이것들을 사용할 수 없습니다. 버그의 발견은 프로그램이 실행될 때 발견하는 것보다 컴파일
할 때 발견하면 버그를 잡는데 소요되는 시간이 짧아집니다.
C++0x에서 새로 생긴 static_assert는 컴파일 할 때 평가됩니다.
static_assert는 특히 컴파일 타임에서 실체화할 템플릿의 전제 조건을 조사할 때 사용하면 좋습니다.
예를 들면 Stack이라는 클래스 템플릿을 정의할 때 템플릿 파라미터로 type과 크기를 사용할 때 크기
가 일정 크기 이상일 때만 컴파일 되기를 바란다면 static_assert를 사용하면 딱 좋습니다.
3.3 static_assert의 형식
static_assert의 형식은 다음과 같습니다.
원형
static_assert “( constant-expression”,“ error-message'') ;
파라미터
“constant-expression”- 검사할 조건 식
“error-message”- 조건이 false일 경우 출력할 error 메시지
결과
constant-expression가 false일 경우 컴파일러는 에러 메시지를 출력합니다.
static_assert는 다음과 같은 경우에 사용하면 유용합니다.
3. static_assert
Visual C++ 10과 C++0x 3. static_assert
16
1. 기본 타입(int, long 등)이나 유저 정의 타입(class, struct 등으로 만든 타입)의 크기를 확인하고
싶을 때
2. 어떤 타입의 최대 크기를 넘어서는지 확인하고 싶을 때
3.4 static_assert 사용 예
3.4.1 프리프로세스 지시어 대체
< 코드 3-2. 상수 값의 크기 조사 >
<코드>
#include <iostream>
using namespace std;
const int MAX_LEVEL = 120;
int main()
{
static_assert( MAX_LEVEL <= 100, "Warring - MAX_LEVEL" );
return 0;
}
</코드>
< 그림 3-1. <코드 3-2>의 컴파일 결과 >
<코드 3-2>는 MAX_LEVEL의 값이 100을 넘으면 컴파일 할 때 에러를 출력합니다.
VC++ 10의 경우 편리한 IntelliSense가 컴파일 하기 전에 붉은 밑줄로 에러가 있음을 사전에 알려주
기도 합니다.
< 그림 3-2. 인텔리센스의 에러 통지 >
3.4.2 템플릿에서 사용
< 코드 3-3. Stack 클래스 템플릿의 최소 스택 크기 조사 >
<코드>
#include <iostream>
using namespace std;
Visual C++ 10과 C++0x 3. static_assert
17
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
template< typename T1, int StackSize >
class MYSTACK
{
static_assert( StackSize >= 10, "Stack Size Error" );
public :
MYSTACK() : data( new T[StackSize] )
{
}
private:
T1* data;
};
int main()
{
MYSTACK< int, 5 > MyStack;
return 0;
}
</코드>
< 그림 3-3. <코드 3-3>의 컴파일 결과 >
3.4.3 변수의 크기 조사
< 코드 3-4. int 타입의 크기 조사 >
<코드>
#include <iostream>
using namespace std;
int main()
{
static_assert( sizeof(int) == 4, "not int size 4" );
return 0;
}
</코드>
static_assert의 개념이나 사용 법이 간단하기 때문에 위의 예제 코드를 보면 어떻게 사용하고, 어디에
사용하면 좋을지 알 수 있으리라 생각합니다.
3.5 핵심 요약
1. assert와 비슷한 조건 조사를 할 수 있음
2. 컴파일 타임 때 사용하여 프로그램 실행 전에 문제를 찾을 수 있음
3. 템플릿 프로그래밍에 사용하면 특히 유용
4. 우측 값 참조 (RValue Reference)
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
18
4.1 C++98/03에서의 실수
C++98/03에서는 너무 과하다 싶을 정도로 추상화와 효율성을 같이 가져가려고 하다가 실수를 범했습
니다. 이 실수는 추상화와 효율성의 조합을 위해 불필요한 복사를 초래 하였습니다.
int와 같은 작은 것의 복사는 괜찮지만 중량급 오브젝트에서는 무시하기 힘듭니다. 다행히 RVO나
NRVO의 도움으로 불필요한 복사 생성자를 제외하여 어느 정도 문제를 경감 시켜주지만 이것 만으로
는 모든 상황에서 과도한 복사 문제를 해결해 주지 못합니다.
의미 없는 복사
위에서 언급한 불필요한 복사의 예를 들면
(a) vector에서 새로운 요소를 추가할 때 확장하는 경우,
(a) string 객체간의 결합 등이 있습니다.
< 그림 4-1. vector의 확장 >
<그림 4-1>과 같이 vector를 확장할 때는 새로운 메모리 공간을 할당한 후 기존 요소를 하나씩 복사한
후 앞서 할당한 영역을 제거합니다. 이것은 영리한 방법은 아닙니다.
vector를 확장할 때 기존 요소를 그냥 그대로 사용할 수 있다면 기존 요소를 복사하지 않아도 되고, 기
존에 사용하던 메모리 영역을 없애 필요도 없습니다. 바꾸어 말하면“복사기로 문서 하나를 복사를 하
는데 복사가 끝난 후 원본은 버린다”라는 경우와 비슷합니다. 원본을 버린다면 궂이 복사를 할 필요도
없습니다. 너무나 멍청한 짓입니다. 그런데 C++ 03까지는 위에서 예를 든 (a)와 (b)를 할 때 복사기로
복사를 하고 원본을 버리는 것과 같은 행동을 했습니다.
(참고로 (a)와 (b)에서는 의미 없는 복사 때문에 메모리 할당, 해제까지 덤(?)으로 합니다)
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
19
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
< 그림 4-2. 원본을 버리는 복사 >
C++0x에서는 이런 불필요한 복사를 방지하는 기능을 제공합니다. 그래서 이전보다 성능적인 측면에서
의 개선이 이루어졌습니다. 불필요한 복사를 방지하여 성능 개선을 이루게 된 것은“우측 값 참조
(RValue Reference)”덕택입니다.
C++0x에서는“우측 값 참조”에 의해서 복사가 아닌 메모리 상의 이동을 할 수 있어서 메모리 할당, 복
사, 해제를 줄일 수 있어서 프로그램의 성능 향상을 얻을 수 있습니다.
우측 값 참조가 간단한 개념은 아니라서 쉽게 이해가 안 될 수도 있지만 제가 처음부터 하나하나 자세
하게 설명할테니 저의 설명을 지금부터 잘 따라오시면 쉽게 이해할 수 있습니다.
4.2 좌측 값(LValue)과 우측 값(RValue)
“우측 값 참조”를 간단하게 정의하면 우측 값의 참조라고 할 수 있습니다. 그래서 우측 값 참조를 알기
위해서는 필연적으로“우측 값”이 무엇을 뜻하는지 알아야 합니다.
이 글을 보는 분들은 C++에서의 좌측 값과 우측 값에 대해서 어떻게 알고 계실지 궁금합니다. 아마 대
부분 좌측 값은“식의 왼쪽에 있는 것”, 우측 값은“식의 오른쪽에 있는 것”이라고 생각하지 않을까 생
각합니다. 이와 같이 우측 값과 좌측 값을 나누는 것은 C 언어라면 맞습니다.
< 그림 4-3. C 언어에서의 좌측 값과 우측 값 >
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
20
<그림 4-3>처럼 좌측 값과 우측 값을 나누는 것은 C 언어에서는 맞지만 C++에서는 틀립니다. C와
C++은 좌측 값과 우측 값 정의가 서로 다릅니다.
C++03의 사양서 3.10/1절에서 좌측 값과 우측 값에 대해서“모든 식은 좌측 값 또는 우측 값이다. 중
요한 것은 좌측 값 또 우측 값이라고 말하는 성질은 식과 관련된 것으로 오브젝트에 대한 것이 아니다”
라고 합니다.
예를 들면 *ptr, ptr[index], ++x 등은 모두 좌측 값입니다. 우측 값은 그것이 존재하는 완전식이 끝나
는 시점에서( ;이 있는 위치) 사라져 버리는 임시 값입니다. 우측 값은 예를 들면 1729, x+y,
std::string“( C++”) 또한 x++ 등입니다.
즉, 식이 끝난 후 계속 존재하는 값은 좌측 값, 식이 끝나면 존재하지 않는 값은 임시 값은 우측 값입니다.
왠지 좀 확실하게 와닿지 않으시죠? 프로그래머에게는 글 보다는 코드가 가장 확실하죠. 아래의 코드로
표현한 그림을 보시면 확실하게 이해할 수 있을 것입니다. =
< 그림 4-4. 좌측 값과 우측 값 예 >
이제 좌측 값과 우측 값이라는 것에 대해서 구분할 수 있을 테니 이번 장의 주제인 우측 값 참조에 대
해서 본격적으로 들어갑니다.
4.3 좌측 값 참조와 우측 값 참조
C++에는 참조라는 라는 것이 있습니다. 사용 방법은‘&’을 사용합니다.
< 그림 4-5. refA는 변수 a를 참조 >
refA는 변수 A를 참조합니다. 이후 refA의 값을 변경하면 refA가 참조하고 있는 변수 A의 값이 변경됩
니다. 즉 refA는 변수 A를 가리키고 있는 것입니다.
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
21
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
이렇게‘&’을 사용한 참조를 정확하게는‘LValue Reference’라고 부릅니다. LValue Reference는
C++ 98 때부터 있는 것으로 지금 저희가 잘 알고 자주 사용하고 있는 것입니다.
C++0x에는 새롭게 RValue Reference라는 것이 생겼습니다.
사용 방법은 기존의 참조와 비슷하여 참조가‘&’을 사용했듯이‘&&’를 사용합니다.
<코드>
int&& RrefA = 119;
</코드>
우측 값 참조는 외견 상으로는 좌측 값 참조에 비해서‘&’을 하나 더 사용한다는 것만 다르지만 의미
상 좌측 값 참조와 우측 값 참조는 다릅니다.
< 그림 4-6. 우측 값 참조와 좌측 값 참조 사용 예 >
<그림 4-6>에서 b)는 좌측 값 참조에 우측 값을 대입했기 때문에 에러가 발생하고, d)는 우측 값 참조
에 좌측 값을 대입했기 때문에 에러가 발생합니다.
이렇게 좌측 값 참조는 좌측 값을 참조해야 하고, 우측 값 참조는 우측 값을 참조해야 합니다.
4.4 Move semantics
4장 첫 머리에서 우측 값 참조 덕택에 불필요한 복사를 없앨 수 있다고 했는데 불필요한 복사를 없앨
수 있는 것은 바로 우측 값 참조의 Move semantics 덕택입니다.
Move semantics에 의해서 C++0x에서는 기존에는 없는‘move 생성자’, ‘move 대입 연산자’라는
것이 생겼습니다. move 생성자와 move 대입 연산자는‘&&’를 사용합니다.
클래스를 정의할 때 move 생성자나 move 대입 연산자를 정의하면 어떤 오브젝트에서 다른 오브젝트
로 리소스를 복사가 아닌 이동 시킬 수 있습니다. 이 이동이라는 것은 오브젝트를 다른 장소의 메모리
로 이동 시키는 것입니다.
‘move 생성자’, ‘move 연산자’는 암묵적으로는 만들어지지 않으면‘복사 생성자’가‘move 생성자’
보다 우선 순위가 높고,‘ 대입 연산자’가‘move 대입 연산자’보다 우선 순위가 높습니다.
복사가 아닌 리소스의 이동은 STL의 vector의 크기를 키울 때 사용할 수 있습니다.
현재의 STL vector는 새로운 요소를 삽입할 때 빈 영역이 없으면 새로운 메모리를 확보한(보통 현재
할당된 메모리의 2배) 후 기존 요소를 복사한 후 새로운 요소를 삽입합니다. 그러나 C++0x에서는
Move semantics에 의해 성능 부하가 큰 복사가 아닌 메모리 상으로의 이동을 합니다.
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
22
< 그림 4-7. C++98/03과 C++0x에서의 vector의 크기 확장 >
C++0x의 모든 STL에는 Move semantics가 적용됩니다. 그래서 기존의 코드를 바꾸지 않고 C++0x
를 사용하는 컴파일러만 사용해도 성능 향상이 이루어집니다. VC 10은 우측 값 참조를 지원하므로 모
든 STL에 Move semantics가 적용 되었습니다.
4.5 Move 생성자와 Move 대입 연산자
앞서 C++0x에서는 우측 값 참조에 의해‘move 생성자’와‘move 대입 연산자’라는 것이 생겼다고
하였습니다. move 생성자와 move 대입 연산자 정의는 아래와 같습니다.
< 코드 4-1. QuestInfo 클래스 >
<코드>
class QuestInfo
{
public:
// 복사 생성자
QuestInfo(const QuestInfo& quest)
: Name(new char[quest.NameLen]), NameLen(quest.NameLen)
{
memcpy(Name, quest.Name, quest.NameLen);
}
// 대입 연산자
QuestInfo& operator=(const QuestInfo& quest)
{
if (this != &quest)
{
if (NameLen < quest.NameLen)
{
// 버퍼를 확보한다
}
NameLen = quest.NameLen;
memcpy(Name, quest.Name, NameLen);
}
return *this;
}
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
23
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
// move 생성자
QuestInfo(QuestInfo&& quest)
: Name(quest.Name), NameLen(quest.NameLen)
{
quest.Name = NULL;
quest.NameLen = 0;
}
// move 대입 연산자
QuestInfo& operator=(QuestInfo&& quest)
{
if( this != &quest )
{
delete Name;
Name = quest.Name;
NameLen = quest.NameLen;
quest.Name = NULL;
quest.NameLen = 0;
}
return *this;
}
private:
char* Name;
int NameLen;
};
</코드>
위의 <코드 4-1>에서 QuestInfo(QuestInfo&& quest)가 move 생성자, QuestInfo&
operator=(QuestInfo&& quest)가 move 대입 연산자입니다.
외견 상으로 기존의 복사 생성자, 대입 연산자와의 차이는 함수 파라메터에서‘&’가 아닌‘&&’을 사용
하는 것입니다.
4.6 Move semantics에 의한 성능 향상
우측 값 참조를 설명할 때 이것 덕분에 프로그램의 성능이 좋아진다고 했습니다.
왜 그럴까요? <코드 4-1>에서 복사 생성자와 Move 생성자, 대입 연산자와 Move 대입 연산자의 구현
을 다시 한번 잘 보시기 바랍니다. 잘 보시면 왜 성능이 좋은지 바로 아실 수 있을 것입니다. 이유를 찾
으셨나요?
앞서 여러 번 우측 값 참조는 Move Semantics에 의해 복사가 아닌 메모리 상의 이동을 한다고 말했
습니다. <코드 4-1>의 Move 생성자와 Move 대입 연산자는 넘겨 받은 인자를 복사하지 않고 메모리
상의 이동을 하고 있습니다.
< 코드 4-2. 복사 생성자와 Move 생성자 >
<코드>
// 복사 생성자
QuestInfo(const QuestInfo& quest)
: Name(new char[quest.NameLen]), NameLen(quest.NameLen)
{
memcpy(Name, quest.Name, quest.NameLen);
}
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
24
// move 생성자
QuestInfo(QuestInfo&& quest)
: Name(quest.Name), NameLen(quest.NameLen)
{
quest.Name = NULL;
quest.NameLen = 0;
}
</코드>
<코드 4-2>의 코드에서 move 생성자 정의에서 굵게 표시한 코드를 보면 복사 생성자와 달리 메모리
주소를 대입한 것을 알 수 있을 것입니다. 이것이 바로 복사가 아닌 이동입니다.
메모리 이동을 한 후 인자로 넘겨진 객체가 사라지더라도 메모리 파괴가 일어나지 않도록 인자로 넘겨
진 객체에는 NULL을 대입하고 있습니다.
이렇게 복사가 아닌 이동을 하는 것은 크기가 작은 오브젝트에서는 큰 의미가 없지만 크기가 큰 오브젝
트에서는 그 차이가 무시할 수 없을 것입니다.
4.7 move 생성자와 move 대입 연산자 사용 예
move 생성자와 move 대입 연산자를 정의한 클래스는 어떻게 동작하는 예제를 통해서 보여드리겟습
니다.
< 코드 4-3. 복사 생성자와 대입 연산자 정의 >
<코드>
#include <iostream>
using namespace std;
class NPC
{
public:
int NPCCode;
string Name;
NPC() { cout << “기본생성자" << endl; }
NPC( int _NpcCode, string _Name ) { cout << “인자있는생성자" << endl; }
NPC(NPC& other) { cout << “복사생성자" << endl; }
NPC& operator=(const NPC& npc) { cout << “대입연산자" << endl; return *this; }
NPC(NPC&& other) { cout << "Move 생성자" << endl; }
NPC& operator=(const NPC&& npc) { cout << "Move 연산자" << endl; return *this; }
};
int main()
{
cout << "1" << endl;
NPC npc1( NPC( 10,"Orge1") );
cout << endl << "2" << endl;
NPC npc2(11,"Orge2");
NPC npc3 = npc2;
cout << endl << "3" << endl;
NPC npc4; NPC npc5;
npc5 = npc4;
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
25
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
cout << endl << "4" << endl;
NPC npc6 = NPC(12, "Orge3");
cout << endl << "5" << endl;
NPC npc7; NPC npc8;
npc8 = std::move(npc7);
getchar();
return 0;
}
</코드>
< 결과 4-3 >
<코드 4-3>의 결과를 보면 move 생성자와 move 대입 연산자를 정의한 NPC 클래스는 우측 값을 사
용했을 때는 move 생성자와 move 대입 연산자가 호출 됨을 알 수 있습니다. 그런데 <코드 4-3>에서
std::move라는 이전까지 보지 못했던 함수를 사용했습니다. move 라는 이름을 사용하는 걸로 봐서
우측 값 참조와 관련된 함수일 것 같지 않나요?
4.8 std::move
<코드>
cout << endl << "5" << endl;
NPC npc7; NPC npc8;
npc8 = std::move(npc7);
</코드>
위 코드는 <코드 4-3>에서 npc8에 npc7 이라는 좌측 값을 대입했는데 결과를 보면 move 대입 연산
자가 사용 되었습니다. move 생성자나 move 대입 연산자는 인자가 우측 값일 때만 사용되므로 일반
적인 대입 연산자가 호출 되야합니다. 그러나 위 코드에서는 move 대입 연산자가 호출 되었습니다. 이
것은 바로 std::move 라는 함수를 사용하였기 때문입니다.
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
26
std::move는 Move Semantics를 위해 이번에 새롭게 추가된 것입니다. 이것은 좌측 값(LValue)을 우
측 값(RValue)으로 타입 캐스팅하기 위해 표준 라이브러리에서 제공하는 함수입니다.
std::move는 아래와 같이 구현되어 있습니다.
<코드>
namespace std {
template <class T>
inline typename remove_reference<T>::type&& move(T&& x)
{
return x;
}
}
</코드>
위 코드를 보면 알 수 있듯이 std::move 라는 것이 뭔가 복잡한 기능을 가진 것이 아니고 단순하게 타
입 캐스팅을 해주는 함수라는 것을 알 수 있습니다.
4.9 VC 10에서의 STL과 우측 값 참조
앞에서 VC 10의 STL에는 우측 값 참조가 적용되었다고 말했습니다. 이 덕분에 기존의 코드를 수정하지
않고 VC 10으로 빌드만 해도 기존에 비해서 성능이 더 좋아집니다.
우측 값 참조가 적용된 VC 10의 STL vector
VC 10의 vector쪽 소스 코드를 보면 이전의 vector에는 없던 코드가 있습니다.
<코드>
……………………
#if _HAS_RVALUE_REFERENCES
vector(_Myt&& _Right)
: _Mybase(_Right._Alval)
{ // construct by moving _Right
_Assign_rv(_STD forward<_Myt>(_Right));
}
………
</코드>
위 코드를 보면 바로 아시겠죠? 네 move 생성자가 정의 되어 있습니다.
< 코드 4-4. 우측 값 참조를 사용한 vector 사용 >
<코드>
vector<int> foo()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
return v;
}
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
27
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
int main()
{
vector<int> v1 = foo();
cout << v1[0] << endl;
return 0;
}
</코드>
<코드 4-4>를 VC 9(visual Studio 2008)와 VC 10에서 디버깅 해보면 vector의 생성자에서 서로 다
른 부분을 호출하는 것을 볼 수 있습니다.
VC 9에서 디버깅을 해보면 다음의 위치에서 브레이크 포인터가 걸립니다.
< 그림 4-8. VC 9의 vector >
위 코드를 보면 복사 생성자에서 받은 vector의 크기를 비교하여 현재 공간이 인자로 받은 vector보다
작으면 재할당을 하고, vector에 있는 모든 요소를 복사합니다.
< 그림 4-9. VC 10의 vector >
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
28
보시는 바와 같이 VC 10에서는 move 생성자가 호출됩니다. 그리고 요소를 복사하지 않고 메모리 상
의 이동을 합니다.
또한 vector에 할당된 공간을 다 사용한 상태에서 새로운 요소를 삽입하면 기존에는 새로운 공간을 할
당 후 저장하고 있던 모든 요소를 복사를 하지만 C++0x에서는 Move Semantics에 의해 메모리 상의
이동을 합니다.
STL의 string
너무 당연하지만 string 클래스도 우측 값 참조가 잘 적용되어 있습니다.
<코드>
string msg1(“Error”);
string msg2(“- Network”);
string msg3(“: Accept”);
string Msg = msg1 + ““ + msg2 + ““ + msg3;
</코드>
string에 만약 우측 값 참조가 적용되지 않았다면 operator+()가 호출될 때마다 임시 문자열이 생성되
어 동적 메모리 할당과 복사가 일어나지만 우측 값 참조를 적용하면 불 필요한 동적 메모리 할당과 해
제 그리고 복사를 제거할 수 있습니다.
4.10 우측 값 참조를 사용할 때 주의할 점
우측 값 참조를 사용하면 Move Semantics에 의해 의도하지 않은 버그가 발생할 수 있습니다.
< 그림 4-10. 좌측 값이 우측 값으로 사용된 이후 >
<그림 4-10>에서 npc7은 좌측 값이지만 std::move 함수에 의해서 우측 값으로 사용되었습니다.
move 대입 연산자는 메모리 상의 이동을 하므로 npc7이 동적 메모리를 사용한 멤버를 가지고 있다면
std::move를 사용한 이후의 npc7의 값은 유효하지 않을 것입니다.
이런 문제는 STL에 우측 값 참조가 적용 되었으므로 vector에서도 이런 문제가 발생합니다.
< 코드 4-5. Vector의 대입 문제 >
<코드>
int main()
{
vector<int> v1;
v1.push_back(10);
v1.push_back(12);
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
29
프로세스 강요 (Process Enactment) 일관된 프로세스를 강요해야 함
가시화 (Visibility) 모든 전반적인 활동에 대한 진행 상황을 볼 수 있어야 함
추적성 (Traceability) 모든 활동이나 산출물 등 연관 관계를 추적할 수 있어야 함
표 1 ALM의 3대 구성 요소
vector<int> v2 = std::move(v1);
cout << v1.size() << endl;
cout << v2.size() << endl;
return 0;
}
</코드>
< 결과 4-5 >
<코드 4-5>에서 v1을 v2에서 move 생성자를 사용하기 위해서 std::move()를 사용했습니다.
std::move는 메모리 이동을 한다고 했습니다. 그래서
<코드>
vector<int> v2 = std::move(v1);
</코드>
이 후에는 v1의 크기는 0이 됩니다.
만약 우측 값 참조의 Move Semantics를 잘 이애하지 못한 상태에서 우측 값 참조를 사용한다면아주
골치 아픈 버그를 만들 수 있습니다. 우측 값 참조에서 사용하는 우측 값은 임시 값이라는 것을 잘 기
억하고 우측 값 참조를 사용해야 합니다.
4.11 우측 값 참조와 좌측 값 참조의 함수 오버로드
우측 값 참조와 좌측 값 참조는 타입이 다르므로 함수 오버로드를 적용할 수 있습니다.
< 코드 4-6. 함수 오버로드 >
<코드>
struct ITEM {};
void GetItem( ITEM& item )
{
cout << "LValue 참조" << endl;
}
void GetItem( ITEM&& item )
{
cout << "RValue 참조" << endl;
}
int main()
{
ITEM item;
GetItem( item );
GetItem( ITEM() );
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
30
getchar();
return 0;
}
</코드>
< 결과 4-6 >
4.12 우측 값 참조는 우측 값이 아니다
우측 값은 임시 값입니다. 그러나 우측 값 참조는 임시 값이 아닙니다.
우측 값을 참조하여 우측 값 참조가 되었더라도 우측 값 참조는 우측 값이 아닙니다.
그래서 우측 값 참조는 우측 값 참조로 초기화 할 수 없습니다.
< 그림 4-11. 우측 값 참조의 성공과 실패 예 >
4.13 Perfect Forwarding
앞선 설명에서 우측 값 참조를 사용할 때 주의할 점을 이야기 했습니다. 좌측 값이 우측 값이 되어 우
측 값 참조로 사용되면 그 좌측 값은 보증할 수 없게 됩니다.
템플릿 프로그래밍에서는 인자 추론이라는 것이 있는데 이 인자 추론에 의해서 좌측 값과 우측 값을 파
라미터로 사용한 함수가 원하는대로 호출되지 않는 문제가 발생합니다.
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
31
< 코드 4-7. >
<코드>
#include <iostream>
struct X {};
void func( X&& t )
{
std::cout << "RValue" << std::endl;
}
void func( X& t )
{
std::cout << "LValue" << std::endl;
}
template<typename T>
void foo(T&& t)
{
func( t );
}
int main()
{
X x;
foo(x);
foo( X() );
getchar();
return 0;
}
</코드>
< 결과 4-7 >
<코드 4-7>의 결과를 보면 인자 추론을 할 때 우선 순위에 의해서 좌측 값을 파라미터로 사용한 함수
가 모두 호출 되었습니다.
이 문제를 해결하기 위해 foo 함수에서 std::move를 사용 하였습니다.
< 코드 4-8 >
<코드>
#include <iostream>
struct X {};
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
32
void func( X&& t )
{
std::cout << "RValue" << std::endl;
}
void func( X& t )
{
std::cout << "LValue" << std::endl;
}
template<typename T>
void foo(T&& t)
{
func( std::move(t) );
}
int main()
{
X x;
foo(x);
foo( X() );
getchar();
return 0;
}
</코드>
< 결과 4-8 >
std::move를 사용하여 foo 함수에서 우측 값 함수를 호출하도록 하였습니다.
그러나 여기에서도 문제가 있습니다. std::move를 사용하는 바람에 foo 함수에 좌측 값을 전달해도 우
측 값 참조로 사용됩니다. 앞서 이야기 했지만 좌측 값이 우측 값 참조로 사용되면 그 좌측 값을 보증
하지 못합니다. <코드 4-8>의 foo 함수는 의도하지 않은 버그를 발생할 수도 있습니다.
이런 문제는 std::forward 라는 표준 라이브러리에서 제공하는 함수를 사용하여 해결할 수 있습니다.
std::forward도 std::move와 같이 타입 캐스팅을 해주는 함수입니다.
std::forward 함수는 좌측 값은 좌측 값으로, 우측 값은 우측 값으로 캐스팅 해 줍니다.
<코드 4-7>과 <코드 4-8>의 문제를 std::forward 함수를 사용하여 아래와 같이 해결했습니다.
< 코드 4-9. std::forward 사용 예 >
<코드>
#include <iostream>
struct X {};
void func( X&& t )
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
33
{
std::cout << "RValue" << std::endl;
}
void func( X& t )
{
std::cout << "LValue" << std::endl;
}
template<typename T>
void foo(T&& t)
{
func( std::forward<T>(t) );
}
int main()
{
X x;
foo(x);
foo( X() );
getchar();
return 0;
}
</코드>
< 결과 4-9 >
std::forward는 아래와 같이 구현 되어 있습니다.
<코드>
namespace std {
template <class T, class U,
class = typename enable_if<
(is_lvalue_reference<T>::value ?
is_lvalue_reference<U>::value :
true) &&
is_convertible<typename remove_reference<U>::type*,
typename remove_reference<T>::type*>::value
>::type>
inline T&&
forward(U&& u)
{
return static_cast<T&&>(u);
}
}
</코드>
Visual C++ 10과 C++0x 4. 우측 값 참조 (RValue Reference)
34
4.14 핵심 요약
1. 식이 끝난 후 계속 존재하는 값은 좌측 값, 식이 끝나면 존재하지 않는 값은 임시 값은 우측 값
2.‘ &’을 사용한 참조를 정확하게는‘LValue Reference’라고 부른다. 사용 방법은 기존의 참조와 비
슷하여 참조가‘&’을 사용했듯이‘&&’를 사용
3. 좌측 값 참조는 좌측 값을 참조하고, 우측 값 참조는 우측 값을 참조한다.
4. 불필요한 복사를 없앨 수 있는 것은 바로 우측 값 참조의 Move semantics 덕택. Move
semantics에 의해서 C++0x에서는 기존에 없는‘move 생성자’, ‘move 대입 연산자’라는 것이
생겼음
5.‘ move 생성자’,‘ move 연산자’는 암묵적으로는 만들어지지 않으면‘복사 생성자’가‘move 생성
자’보다 우선 순위가 높고,‘ 대입 연산자’가‘move 대입 연산자’보다 우선 순위가 높다.
6. 표준 라이브러리에서 제공하는 std::move를 사용하면 좌측 값을 우측 값으로 타입 캐스팅 할 수 있다.
7. 우측 값 참조와 좌측 값 참조는 타입이 다르므로 함수 오버로드를 적용할 수 있다.
8. 우측 값 참조는 우측 값이 아니다.
9. std::forward 함수는 좌측 값은 좌측 값으로, 우측 값은 우측 값으로 캐스팅 해 준다.
Visual C++ 10과 C++0x 5. 람다 (lambda)
35
람다는 람다 함수 또는 이름 없는 함수라고 부르며 그 성질은 함수 오브젝트와 같습니다. 규격
에서는 람다는 특별한 타입을 가지고 있다고 합니다. 그렇지만 decltype나 sizeof에서는 사용
할 수 없습니다.
C++0x는 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 증대 되었습니다.
5.1 C#과 람다
C#이나 동적 프로그래밍 언어를 공부 하신 분들은 람다에 대해서 들어보셨을 것입니다. 람다를 잘
모르는 분들을 위해서 현재 가장 쉽게 람다를 접할 수 있는 C#을 통해서 람다의 사용 예를 들어 보겠
습니다.
C#에서의 람다
람다는 식과 문을 포함하여 대리자나 식 트리 형식을 만드는데 사용할 수 있는 익명함수입니다.
형식은 다음과 같습니다.
입력 매개 변수 => 식 or 문
람다는 주로 어떤 라이브러리의 식과 결합해서 사용할 식을 만들 때 사용합니다. 람다가 없다면 다른
식과 결합하기 위해서는 따로 함수를 만들어서 사용해야 하므로 거추장스러워 지는데 람다를 사용하면
아주 간단하게 구현할 수 있습니다.
< 코드 5-1. 이름 중 문자 길이가 TextLength 보다 작은 이름 >
<코드>
string[] MobNames = { "Babo", "Cat", "Orge", "Tester", "CEO" };
int TextLength = 4;
// 람다 식 사용
var ShortNames1 = MobNames.Where(MobName => MobName.Length < TextLength);
// foreach 사용
List<string> ShortNames2 = new List<string>();
foreach(string MobName in MobNames)
{
if (MobName.Length < TextLength)
{
ShortNames2.Add(MobName);
}
}
</코드>
5. 람다 (lambda)
Visual C++ 10과 C++0x 5. 람다 (lambda)
36
<코드 5-1>을 보면 람다를 사용하여 한 줄로 끝나는 것을 람다를 사용하지 않으면 List 컨테이너를 생
성하고 foreach 구문을 사용하여 이름을 하나씩 조사하여 List 컨테이너에 추가해야 되는 코드가 필요
해집니다.
C#에서 람다가 가장 유용하게 사용되는 부분은 LINQ 일겁니다.
만약 LINQ에서 람다를 사용하지 않게 된다면 LINQ를 사용하기가 꽤 힘들어질 것입니다.
5.2 C++에서 STL 알고리즘을 사용할 때 불편했던 점
기존의 C++에서 STL의 find_if, sort 등의 알고리즘을 사용할 때 특정 조건자를 사용하기 위해서는 펑
터(functer)를 만들어야 합니다. 그런데 이것 때문에 따로 펑터를 만드는 것이 귀찮아서 보통 그냥 따로
함수를 만들어서 구현하기도 합니다.
< 코드 5-2. 펑터를 사용한 find_if 알고리즘 >
<코드>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
class User
{
public:
User() : m_bDie(false) {}
~User() {}
void SetDie() { m_bDie = true; }
bool IsDie() { return m_bDie; }
private:
bool m_bDie;
};
struct FindDieUser
{
bool operator() (User& tUser) const { return tUser.IsDie(); }
};
int main()
{
vector< User > Users;
User tUser1; Users.push_back(tUser1);
User tUser2; tUser2.SetDie(); Users.push_back(tUser2);
User tUser3; Users.push_back(tUser3);
vector< User >::iterator Iter;
Iter = find_if( Users.begin(), Users.end(), FindDieUser() );
return 0;
}
</코드>
<코드 5-2>는 유저들 중 죽은 유저를 찾기 위해 find_if 알고리즘을 사용했는데 이것 때문에 struct로
펑터를 만들어야 합니다. 그런데 펑터를 만들지 않고 그냥 간단하게 기술할 수 있으면 좋겠죠?
Visual C++ 10과 C++0x 5. 람다 (lambda)
37
혹시 <코드 5-1>에서 C#에서는 람다 식으로 쉽게 구현했던 것을 보았는데 <코드 5-2>의 find_if 알고
리즘에서도 이런 것을 사용하면 좋지 않을까요? C++0x에서 람다가 새로 생겼습니다.
5.3 C++에서의 람다 사용 법
5.3.1 문법
람다의 문법은 아래와 같습니다.
<코드>
int main()
{
[] // lambda capture
() // 함수의 인수 정의
{} // 함수의 본체
() // 함수 호출;
}
</코드>
아래는 람다를 사용한 간단한 예입니다.
<코드>
int main()
{
[]{ std::cout << “Hello, TechDay!”<< std::endl; }();
}
<코드>
5.3.2 람다를 변수에 대입
auto를 사용하면 람다를 변수에 대입할 수 있습니다.
< 코드 5-3. 람다를 변수에 대입 >
<코드>
#include <iostream>
int main()
{
auto func = [] { std::cout << "Hello, TechDay!" << std::endl; };
func();
getchar();
return 0;
}
</코드>
< 결과 5-3 >
Visual C++ 10과 C++0x 5. 람다 (lambda)
38
5.3.3 람다를 함수의 인자로 사용하기
템플릿 프로그래밍에서 함수의 인자로 람다를 사용할 수 있습니다.
< 코드 5-4. 함수의 인자로 사용 >
<코드>
#include <iostream>
template< typename Func >
void Test( Func func )
{
func();
}
int main()
{
auto func = [] { std::cout << "Gunz2 is Greate Game!" << std::endl; };
Test( func );
getchar();
return 0;
}
</코드>
< 결과 5-4 >
5.3.4 람다의 파라미터
람다는 일반 함수처럼 파라미터를 정의할 수 있습니다.
< 코드 5-5. 파라미터 사용 >
<코드>
#include <iostream>
int main()
{
auto func = []( int n ) { std::cout << "Number : " << n << std::endl; };
func( 333 );
func( 7777 );
getchar();
return 0;
}
</코드>
< 결과 5-5 >
Visual C++ 10과 C++0x 5. 람다 (lambda)
39
5.3.5 반환 값 넘기기
람다는 당연하게 반환 값을 넘길 수도 있습니다. 반환 값의 타입은 명시적으로 지정할 수도 있고, 암묵
적으로 타입을 추론할 수 있습니다.
< 코드 5-6. 반환 값 사용 >
<코드>
int main()
{
auto func1 = [] { return 3.14; };
auto func2 = [] ( float f ) { return f; };
auto func3 = [] () -> float{ return 3.14; };
float f1 = func1();
float f2 = func2( 3.14f );
float f3 = func3();
return 0;
}
</코드>
<코드 5-6>을 컴파일하면 아래와 같은 경고가 표시 됩니다.
< 그림 5-1. <코드 5-6>의 컴파일 경고 >
<그림 5-1>의 경고 내용을 보면 func1가 반환하는 3.14은 double 타입으로 추론되었고, func2는 float
타입의 파라미터를 반환하고 있어서 float로 추론되었습니다. func3는 반환 타입을 float로 명시적으로
지정하였기 때문에 3.14를 반환하지만 func1과 같이 dobule이 아닌 float 타입으로 반환 되었습니다.
반환 타입을 명시적으로 반환 할때는 <코드 5-6>의 func3 처럼
-> 반환타입
으로 지정합니다.
5.4 캡쳐
람다를 사용할 때 람다 외부에 정의되어 있는 변수를 람다 내부에서 사용하고 싶을 때는 그 변수를 캡
쳐(Capture)합니다.
캡쳐는 참조나 복사로 전달이 가능합니다. 참조를 사용하는 경우는‘&’을 사용하고, 복사로 전달할 때
는 그냥 변수 이름을 기술합니다.
Visual C++ 10과 C++0x 5. 람다 (lambda)
40
람다 표현의
<코드>
[](파라메터) { 식 }
</코드>
에서 앞의‘[]’사이에 캡쳐 할 변수를 기술합니다.
참조로 캡쳐
<코드 5-7>은 람다 외부의 변수를 참조로 캡쳐하고 있습니다.
< 코드 5-7. lambda에서 캡쳐 사용 >
<코드>
int main()
{
vector< int > Moneys;
Moneys.push_back( 100 );
Moneys.push_back( 4000 );
Moneys.push_back( 50 );
Moneys.push_back( 7 );
int TotalMoney1 = 0;
for_each(Moneys.begin(), Moneys.end(), [&TotalMoney1](int Money) {
TotalMoney1 += Money;
});
cout << "Total Money 1 : " << TotalMoney1 << endl;
return 0;
}
</코드>
< 결과 5-7 >
람다 식이 외부에 있는 TotalMoney1 변수를 참조로 캡쳐하여 Moneys에 있는 값을 모두 더하고 있습
니다.
<코드 5-8>과 같이 포인터 변수를 전달할 수도 있습니다.
< 코드 5-8. 캡쳐로 포인터 전달 >
<코드>
………….
int TotalMoney2 = 0;
int* pTotalMoney2 = &TotalMoney2;
for_each(Moneys.begin(), Moneys.end(), [pTotalMoney2](int Money) {
*pTotalMoney2 += Money;
});
cout << "Total Money 2 : " << TotalMoney2 << endl;
………..
Visual C++ 10과 C++0x 5. 람다 (lambda)
41
</코드>
< 결과 5-8 >
복사로 캡쳐
그럼 TotalMoney1 변수를 값으로 전달하면 어떻게 될까요?
<코드>
for_each(Moneys.begin(), Moneys.end(), [TotalMoney1](int Money) {
TotalMoney1 += Money;
});
</코드>
아래와 같은 컴파일 에러가 발생합니다.
“error C3491: 'TotalMoney1': a by-value capture cannot be modified in a non-mutable
lambda”
만약 꼭 복사로 캡쳐한 변수를 람다 내부에서 변경을 해야한다면 mutable 키워드를 사용하면 에러 없
이 컴파일 할 수 있습니다.
<코드>
[=]() mutable{ std::cout << x << std::endl; x = 200; }();
</코드>
단 컴파일은 되지만 람다 내부에서 변경한 외부 변수의 값은 람다를 벗어나면 람다 내부에서 변경하기
전의 원래의 값이 됩니다.
복수의 변수 캡쳐
<코드 5-7>에서는 하나의 변수만을 캡쳐했지만 복수의 변수를 캡쳐하는 것도 가능할까요? 네 당연히
가능합니다.
‘[]’사이에 캡쳐 할 변수를 선언하면 됩니다.
<코드>
[ &Numb1, &Numb2 ]
</코드>
그럼‘[&]’로 하면 어떻게 될까요? 이렇게 하면 람다 식을 정의한 범위 내에 있는 모든 변수를 캡쳐할
수 있습니다. 또 람다 외부의 모든 변수를 복사하여 캡쳐할 때는 [=]을 사용합니다.
Visual C++ 10과 C++0x 5. 람다 (lambda)
42
< 코드 5-9. 람다 외부의 모든 변수를 참조로 캡쳐하기 >
<코드>
int main()
{
vector< int > Moneys;
Moneys.push_back( 100 );
Moneys.push_back( 4000 );
Moneys.push_back( 1001 );
Moneys.push_back( 7 );
int TotalMoney1 = 0;
int TotalBigMoney = 0;
// Money가 1000 보다 크면 TotalBigMoney에 누적합니다.
for_each(Moneys.begin(), Moneys.end(), [&](int Money) {
TotalMoney1 += Money;
if( Money > 1000 ) TotalBigMoney += Money;
});
cout << "Total Money 1 : " << TotalMoney1 << endl;
cout << "Total Big Money : " << TotalBigMoney << endl;
return 0;
}
</코드>
< 결과 5-9 >
default 캡쳐
람다 외부의 모든 변수를 참조(또는 복사)로 참조하고 일부는 복사(또는 참조)로 캡쳐할 수 있습니다. 그
러나 같은 변수를 캡쳐하거나 default 캡쳐한 일부 변수를 같은 방식(참조 또는 복사)으로 캡쳐할 수 없
습니다.
< 그림 5-2. default 캡쳐 >
Visual C++ 10과 C++0x 5. 람다 (lambda)
43
5.5 클래스에서 람다 사용
클래스의 멤버 함수 내에 람다 식을 정의하고, 이 람다 식에서 해당 클래스의 멤버를 호출 할 수 있습
니다. 클래스 멤버 내의 람다 식은 해당 클래스에서는 friend로 인식하므로 람다 식에서 private 멤버
의 접근도 할 수 있습니다. 그리고 클래스의 멤버를 호출할 때는‘this’를 캡쳐합니다.
< 코드 5-10. 클래스 멤버 호출 >
<코드>
class NetWork
{
public:
NetWork()
{
SendPackets.push_back(10);
SendPackets.push_back(32);
SendPackets.push_back(24);
}
void AllSend() const
{
for_each(SendPackets.begin(), SendPackets.end(), [this](int i){ Send(i); });
}
private:
vector< int > SendPackets;
void Send(int PacketIndex) const
{
cout << "Send Packet Index : " << PacketIndex << endl;
}
};
int main()
{
NetWork().AllSend();
return 0;
}
</코드>
< 결과 5-10 >
<코드 5-10>의 Network 클래스의 멤버 함수 AllSend()에서 람다를 사용했습니다.
<코드>
for_each( SendPackets.begin(), SendPackets.end(),
[this](int i){ Send(i); });
</코드>
위와 같이‘[]’사이에‘this’를 기술하였고, Network 클래스의 private 멤버 함수인 Send를 호출하고
있습니다. 당연히 멤버 변수도 호출 할 수 있습니다.
Visual C++ 10과 C++0x 5. 람다 (lambda)
44
5.6 STL의 find_if에서 람다 사용
그럼 <코드 5-2>에서 펑터를 정의하여 사용했던 것을 람다를 사용하는 것으로 바꾸어 보겠습니다.
< 코드 5-11. find_if 알고리즘에서 lambda 사용 >
<코드>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
class User
{
public:
User() : m_bDie(false) {}
~User() {}
void SetIndex(int index) { m_Index = index; }
int GetIndex() { return m_Index; }
void SetDie() { m_bDie = true; }
bool IsDie() { return m_bDie; }
private:
int m_Index;
bool m_bDie;
};
int main()
{
vector< User > Users;
User tUser1; tUser1.SetIndex(1); Users.push_back(tUser1);
User tUser2; tUser2.SetIndex(2); tUser2.SetDie();
Users.push_back(tUser2);
User tUser3; tUser3.SetIndex(3); Users.push_back(tUser3);
vector< User >::iterator Iter;
Iter = find_if( Users.begin(), Users.end(), [](User& tUser) -> bool { return
true == tUser.IsDie(); } );
cout << "found Die User Index : " << Iter->GetIndex() << endl;
return 0;
}
</코드>
< 결과 5-11 >
find_if 알고리즘을 사용하여 죽은 유저를 찾기 위해서 <코드 5-2>에서는 펑터를 정의한 후 사용하였지만
<코드>
struct FindDieUser
{
bool operator() (User& tUser) const { return tUser.IsDie(); }
};
Visual C++ 10과 C++0x 5. 람다 (lambda)
45
Iter = find_if( Users.begin(), Users.end(), FindDieUser() );
</코드>
<코드 5-11>은 람다를 사용하여 한 줄로 간단하게 끝내버렸습니다.
<코드>
Iter = find_if( Users.begin(), Users.end(), [](User& tUser) -> bool {
return true == tUser.IsDie();
} );
</코드>
예전에는 알고리즘을 사용하려면 펑터를 정의해야 되기 때문에 귀찮아서 알고리즘 사용이 꺼려졌는데
람다 덕분에 알고리즘 사용이 너무나 간편해졌습니다. 제 생각에 람다를 가장 자주 사용하는 부분이 바
로 STL의 알고리즘을 사용할 때가 아닐까 생각합니다.
<참고>
STL의 알고리즘 사용과 성능
컨테이너의 요소를 검색을 할 때 STL의 알고리즘을 사용하는 것이 본인이 직접 for 문이나 while 문을
사용하여 순회해서 찾는 것 보다 성능이 더 좋습니다. 그런데 알고리즘을 사용하려면 펑터를 정의해야
하기 때문에 귀찮아서 알고리즘을 사용하지 않는 경우가 적지 않습니다.
그러나 VC 10부터는 람다를 사용할 수 있으므로 STL의 알고리즘을 쉽게 사용할 수 있습니다. 람다를
사용하는 것은 펑터를 사용하는 것과 같으므로 같은 이점을 얻을 수 있습니다.
티티에프님의 Vector Container Iterating 속도 비교( http://npteam.net/775 )
</참고>
5.7 람다 식을 STL 컨테이너에 저장
람다 식을 STL의 function을 사용하여 STL 컨테이너에 저장할 수 있습니다.
아래는 int를 반환하는 람다 식을 vector에 저장하여 사용하는 것입니다.
< 코드 5-12. 람다 식을 vector 컨테이너에 저장 >
<코드>
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;
int main()
{
vector<function<int()>> v;
v.push_back( [] { return 100; } );
v.push_back( [] { return 200; } );
cout << v[0]() << endl;
cout << v[1]() << endl;
}
</코드>
Visual C++ 10과 C++0x 5. 람다 (lambda)
46
< 결과 5-12 >
5.8 람다를 반환하는 함수
5.7에서 람다를 vector에 저장하기 위해 STL의 function을 사용하였는데 이것을 또 다른 방법으로 사
용하면 람다를 반환 값으로 사용하는 함수에서도 사용할 수 있습니다.
< 코드 5-13. 람다를 반환하는 함수 >
<코드>
#include <iostream>
#include <functional>
#include <string>
std::function< void() > f()
{
std::string str("lambda");
return [=] { std::cout << "Hello, " << str << std::endl; };
}
int main()
{
auto func = f();
func();
f()();
return 0;
}
</코드>
5.9 람다에서의 재귀
람다는 재귀도 가능합니다.
< 코드 5-14. 람다의 재귀 >
<코드>
int main()
{
function<int(int)> fact = [&fact](int x) {
return x == 0 ? 1 : x * fact(x - 1);
};
cout << fact(3) << endl;
}
</코드>
fact 변수를 참조로 넘겨서 재귀를 하고 있습니다.
Visual C++ 10과 C++0x 5. 람다 (lambda)
47
지금까지 람다의 다양한 사용 방법을 설명 하였습니다. 람다를 사용하여 C++로 다양하게 표현할 수 있
는 것을 보았을 것입니다. 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 좋아졌습니다.
참고로 Visual C++ 10에서는 Concurrency Runtime(ConRT)라는 것이 새로 추가 되었습니다. 이것
은 템플릿으로 만들어진 것으로 람다가 많이 사용 되었다고 합니다.
5.10 핵심 요약
1. 람다는 람다 함수 또는 이름 없는 함수라고 부릅며 함수 오브젝트이다.
2. 규격에서 람다는 특별한 타입을 가지고 있다고 한다. 단 decltype나 sizeof에서는 사용 불가
3. 람다를 정의한 곳의 외부 변수를 람다 내부에서 사용하고 싶을 때는 캡쳐한다.
4. 외부 변수를 참조 또는 복사로 캡쳐할 수 있다.
5. 클래스에서도 람다를 사용할 수 있다. 클래스는 람다를 friend로 인식한다.
6. 람다 덕택에 C++의 표현력이 이전보다 훨씬 더 증대 되었다.
6. decltype
Visual C++ 10과 C++0x 6. decltype
48
6.1 템플릿 프로그래밍 시 문제
템플릿 타입(type)이랑 템플릿 메타 프로그래밍 등에서 함수의 반환 형이 복잡하게 정의 되어 있는 경
우 타입을 단순하게 기술할 수 없습니다.
C++0x에서는 이런 문제를 풀기 위해 auto와 decltype 두 개를 제공합니다.
decltype은 auto 처럼 식의 타입을 컴파일 할 때 결정할 수 있습니다.
6.2 decltype 사용하기
사용 예는 아래와 같습니다.
< 코드 6-1. decltype 사용 예 >
<코드>
int Hp;
decltype(Hp) NPCHp = 5;
decltype(Hp + NPCHp) TotalHp;
decltype(Hp*) pHp = &Hp;
</코드>
decltype(Hp) NPCHp = 5;
는 Hp가 int 타입이므로 int NPCHp = 5로 컴파일 할 때 결정됩니다.
또 decltype(Hp + NPCHp) TotalHp;
는 int와 int의 덧셈이므로 int TotalHp;로 됩니다.
decltype(Hp*) pHp = &Hp;는 int* pHp = &Hp가 됩니다.
또한 함수의 반환 타입으로도 사용할 수 있습니다.
< 코드 6-2. 함수의 반환 타입으로 decltype 사용 >
<코드>
int foo();
decltype(foo()) value;
</코드>
decltype(foo()) value;는 int value;로 됩니다.
Visual C++ 10과 C++0x 7. nullptr
49
nullptr은 C++0x에서 추가된 키워드로 널 포인터(Null Pointer)를 나타냅니다.
7.1 nullptr이 필요한 이유
C++03까지는 널 포인터를 나타내기 위해서는 NULL 매크로나 상수 0을 사용하였습니다.
그러나 NULL 매크로나 상수 0을 사용하여 함수에 인자로 넘기는 경우 int 타입으로 추론되어 버리는
문제가 발생하기도 합니다.
< 코드 7-1. 함수 인자 추론 문제 >
<코드>
#include <iostream>
using namespace std;
void func( int a )
{
cout << "func - int " << endl;
}
void func( double *p )
{
cout << "func - double * " << endl;
}
int main()
{
func( static_cast<double*>(0) );
func( 0 );
func( NULL );
getchar();
return 0;
}
</코드>
< 결과 7-1 >
첫 번째 func 호출에서는 double* 로 캐스팅을 해서 의도했던 func이 호출 되었습니다. 그러나 두 번
째와 세 번째 func 호출의 경우 func( doube* p ) 함수에 널 포인터를 파라미터로 넘기려고 했는데
의도하지 않게 컴파일러는 int로 추론하여 func( int a )가 호출 되었습니다.
바로 이와 같은 문제를 해결하기 위해서 nullptr 이라는 키워드가 생겼습니다.
7. nullptr
Visual C++ 10과 C++0x 7. nullptr
50
7.2 nullptr 구현 안
C++0x에서 nullptr의 드래프트 문서를 보면 nullptr은 아래와 같은 형태로 구현 되어 있습니다.
< 코드 7-2. nullptr 구현 클래스 >
<코드>
const class {
public:
template <class T>
operator T*() const
{
return 0;
}
template <class C, class T>
operator T C::*() const
{
return 0;
}
private:
void operator&() const;
} nullptr = {};
</코드>
7.3 nullptr 사용 방법
사용방법은 너무 너무 간단합니다. ^^
그냥 예전에 널 포인터로 0이나 NULL을 사용하던 것을 그대로 대처하면 됩니다.
<코드>
char* p = nullptr;
</코드>
<코드 7-1>에서 널 포인트를 파라미터로 넘겨서 func( double* p )가 호출하게 하기 위해서는
<코드>
func( nullptr );
</코드>
로 호출하면 됩니다.
7.4 nullptr의 올바른 사용과 틀린 사용 예
올바른 사용
<코드>
char* ch = nullptr; // ch에 널 포인터 대입.
sizeof( nullptr ); // 사용 할 수 있습니다. 참고로 크기는 4 입니다.
Visual C++ 10과 C++0x 7. nullptr
51
typeid( nullptr ); // 사용할 수 있습니다.
throw nullptr; // 사용할 수 있습니다.
</코드>
틀린 사용
<코드>
int n = nullptr; // int에는 숫자만 대입가능한데 nullptr은 클래스이므로 안됩니다.
Int n2 = 0
if( n2 == nullptr ); // 에러
if( nullptr ); // 에러
if( nullptr == 0 ); // 에러
nullptr = 0; // 에러
nullptr + 2; // 에러
</코드>
VC++ 10에서는 예전처럼 널 포인터를 나타내기 위해서 0이나 NULL 매크로를 사용하지 말고 꼭
nullptr을 사용하여 함수나 템플릿에서 널 포인터 추론이 올바르게 되어 C++을 더 효율적으로 사용하
기 바랍니다.
왜 nullptr 이라고 이름을 지었을까?
nullptr을 만들 때 기존의 라이브러리들과 이름 충돌을 최대한 피하기 위해서 구글로 검색을 해보니
nullptr로 검색 결과가 나오는 것이 별로 없어서 nullptr로 했다고 합니다.
제안자 중 한 명인 Herb Sutter은 현재 Microsoft에서 근무하고 있는데 그래서인지 C++/CLI에서는
이미 nullptr 키워드를 지원하고 있습니다.
8. 참고
Visual C++ 10과 C++0x 8. 참고
52
C++0x
http://en.wikipedia.org/wiki/C%2B%2B0x
static_assert
http://en.wikipedia.org/wiki/C%2B%2B0x#Static_assertions
Rvalue Reference
http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-
part-2.aspx
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
http://www.artima.com/cppsource/rvalue.html
http://cpplover.blogspot.com/2009/11/rvalue-reference_23.html
http://cpplover.blogspot.com/2009/11/rvalue-reference_25.html
lambda
http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0xfeatures-
in-vc10-part-1.aspx
http://cpplover.blogspot.com/2009/11/lambda.html
http://cpplover.blogspot.com/2009/12/lambda.html
decltype
http://blogs.msdn.com/vcblog/archive/2009/04/22/decltype-c-0x-features-in-vc10-part-3.aspx
nullptr
http://d.hatena.ne.jp/faith_and_brave/20071002/1191322319
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
http://ja.wikibooks.org/wiki/More_C%2B%2B_Idioms/nullptr
http://d.hatena.ne.jp/KZR/20080328/p1
저자 : 최흥배 마이크로소프트 Visual C++ MVP
현재 온라인 게임 개발 회사 마이에트 엔터테인먼트에서 온라인 게임의 서버 프로그램을 개발하고
있으며, Microsoft Visual C++ MVP로도 활동하고 있습니다. 프로그래밍 언어로 C++와 C#을 좋아
하고 요즘은 병렬 프로그래밍과 클라우드 컴퓨팅, 스마트 폰 프로그래밍에 많은 관심을 가지고 있습
니다. 블로그(http://jacking.tistory.com/)와 트위터(@jacking75)를 통해서 게임 이야기나 프로그래밍,
게임 개발에 대한 정보를 다른 개발자들과 공유하고 있습니다.

한국마이크로소프트(유)
서울특별시 강남구 대치동 892번지 포스코 센터 서관 5층 (우) 135-777
고객지원센터 : 1577-9700, 제품홈페이지 : http://www.microsoft.com/visualstudio, 개발자포털 : http://msdn.microsoft.com

반응형
반응형

.NET 으로 프로젝트를 만들고, 배포를 하는 방법은 Click Once같은 방법도 있지만 이번에는 Windows Installer 배포에 대해 알아보겠습니다. 기존에 .Net으로 프로젝트 개발만 해보았지, 배포는 신경을 쓰지 않아 잘 모르다가, 이번에 VS2008에서 기본으로 제공되는 배포 프로젝트의 사용법을 알아 보았습니다.
아래 링크를 따라가면 배포프로젝트에 관한 MSDN 도움말을 보실 수 있습니다.
http://msdn.microsoft.com/ko-kr/library/206sadcd(VS.80).aspx

우선 배포할 프로그램을 준비합니다. 저는 간단한 윈폼 프로젝트로 하겠습니다.

VS2008에서 새 프로젝트를 추가 하고, 기타 프로젝트 형식 -> 설치 및 배포 -> 설치프로젝트를 선택합니다.

다음과 같이 배포 프로젝트가 생성됩니다.

솔루션 탐색기와 속성 창을 보면 아래와 같이 프로젝트와, 여러 속성이 보이게 됩니다.
이제 이 속성들의 의미를 하나하나 알아보겠습니다.
▷AddRemoveProgramIcon : 제어판 -> 프로그램 추가/제거에 표시될 아이콘을 등록합니다.
▷Author : 프로젝트 작성자의 이름을 등록합니다.
▷Desciption : 설치 관리자에 관한 설명을 등록합니다.
▷DetectNewerInstalledVersion : 프로그램 설치시 버전 비교를 통해 새 버전인지 확인 해 줍니다. 이미 설치 되있을 경우 설치 되어있다고 알려주기도 합니다.
▷Keyword : 설치 관리자를 검색하는데 사용 할 키워드를 지정합니다.
▷Localization : 로케일을 적용한다고 하는데, 글로벌 프로그램이 아니라면 신경 안써도 될 듯 합니다.
▷Manufacturer : 제조업체의 이름을 지정합니다
▷MunufacturerUrl : 제조업체의 홈페이지 링크를 지정.
▷PostBuildEvent : 배포프로젝트를 빌드한 후에 실행 할 명령줄을 지정합니다.
▷PreBuildEvent : 배포프로젝트를 빌드하기 전에 실행 할 명령줄을 지정합니다.
▷ProductCode : 응용프로그램의 고유 식별자(GUID)를 지정합니다.
▷ProductName : 프로그램의 공개 이름을 지정합니다.
▷RemovePrevionsVersions : 설치시 이전버전을 삭제 할지를 지정합니다.
▷RunPostBuildEvent : PostBuildEvent 속성에서 지정된 명령줄을 실행할 시기를 결정합니다.
▷SearchPath : 개발 컴퓨터의 어셈블리, 파일 또는 병합 모듈을 검색하는 데 사용되는 경로를 지정합니다.
Subject : 프로그램을 설명하는 추가정보를 지정합니다.
▷SupportPhone : 전화번호를 지정합니다.
▷SupportUrl : 마찬가지로 추가 설명을 하는 웹사이트 주소
TargetPlatform : 프로그램이 실행될 플랫폼을 지정.
▷Title : 설치 관리자의 제목을 지정합니다.
▷UpgradeCode : 프로그램 버전이 여러가지 일때 고유식별자를 지정합니다.
▷Virsion : 버전을 지정합니다.




속성 참 많네요.. 정작 중요한건 ProductName, Title, Author, MAnufacturer 정도 입니다.

이제 파일시스템 탭으로 이동하여 대상 컴퓨터의 파일 시스템 -> 응용 프로그램 폴더를 선택한 후 속성창을 확인합니다.

DefalutLocation이라는 속성에 [ProgramFilesFolder][Manufacturer]\[ProductName] 라고 되어 있습니다. 인스톨시 프로그램이 설치될 폴더 인데요. 이 설정으로 하게 되면 프로그램파일 폴더 밑에 제조회사명 밑에 프로그램 이름 폴더 안에 깔리게 되겠습니다. 맘에 드는 폴더로 변경 하시면 됩니다.

이제 파일을 추가 해 보겠습니다. 응용 프로그램 폴더에서 마우스 오른쪽 버튼을 누르면 파일이나 폴더등을 추가 할 수 있습니다. 미리 만들어 놓은 샘플 어플리케이션을 추가 하겠습니다.


이렇게 하면 앞서 지정된 설치 폴더에 MainApp.exe가 설치 됩니다. 명색이 인스톨 프로그램인데 이것만 지정하면 안되겠죠? 위에 사용자 바탕화면사용자 프로그램 메뉴가 보입니다.
말 그래로 사용자 바탕화면은 바탕화면에 설치 될 파일을 지정 할 수 있고, 사용자 프로그램 메뉴는 [시작]->[프로그램]의 폴더나 파일을 지정 할 수 있습니다.
바탕화면과 프로그램 메뉴에 MainApp.exe의 바로가기를 넣어주면 클라이언트가 아주 편리 할 것 같습니다.
MainApp.exe를 마우스 오른쪽 버튼으로 클릭하여 바로가기를 만듭니다. 저는 두개를 만들어 이름을 원하는데로 변경한 후 하나는 사용자 바탕화면으로 끌어다 놓았고, 사용자 프로그램 메뉴에는 [새폴더]를 하나 추가 하여, 그 안에 넣었습니다. 그리고 아이콘 파일(.ico)도 하나 추가하여 바로가기의 속성중 Icon에 연결 시켜 줍니다. 그럼 바로가기가 우리가 지정한 아이콘으로 생성 됩니다.

이제 언인스톨 기능을 하는 바로가기도 지원해 주어야 좀 더 있어보일 것입니다.
바탕화면에서 텍스트문서(txt)를 하나 추가해서 확장자를 bat로 바꾸어 줍니다. 편집기로 파일을 열어 Msiexec /x {ProductCode} 를 추가 해 줍니다. 여기서 ProductCode는 위에 프로젝트 속성중에 있던 코드 입니다. 지금 예제 대로 하면 Msiexec /x {A1715BBB-A953-4F01-B788-168542ED2BC3} 이 되겠네요.

현재 배포하는 샘플 프로그램이 매우 간단하여 파일이 하나이지만, 대부분의 응용은 여러 DLL을 포함하고 있을것입니다. 그리고 각 파일마다 설치하고 싶은 경로가 다를 수 있는데, 파일시스템 탭의 대상 컴퓨터의 파일 시스템을 오른쪽 버튼으로 누르면 특수폴더 추가에서 원하는 폴더를 추가 할 수 있습니다.

이제 이 파일을 응용프로그램 폴더에 포함 시켜주고, 마찬가지 방법으로 바로 가기를 만들어서 원하는 곳에 추가 시켜 줍니다. 프로젝트를 다시 빌드 하고 테스트 해보겠습니다. 프로젝트 폴더의 Relese 폴더에 들어가니 파일이 두개 보입니다.(Relese모드로 빌드 했을 경우, Debug 모드 일경우 Debug 폴더에 생성)
Setup.exe를 더블 클릭하니 설치가 잘 됩니다.
설치시 생긴 바로가기 아이콘으로 Uninstall도 잘되는지 확인해 보겠습니다.

반응형
반응형

VS 2008 에서 빌드한 실행파일이 다른 컴터에서 실행이 안될때

http://www.microsoft.com/downloads/details.aspx?displaylang=ko&FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf




Microsoft

Microsoft Visual C++ 2008 재배포 가능 패키지(x86)




Microsoft Visual C++ 2008 재배포 가능 패키지(x86)는 Visual C++ 2008이 설치되지 않은 컴퓨터에서 Visual C++로 개발된 응용 프로그램을 실행하는 데 필요한 Visual C++ 라이브러리의 런타임 구성 요소를 설치합니다.

간단 정보

버전:x86게시 날짜:2008-02-04
파일 이름크기
vcredist_x86.exe1.7 MB다운로드
Microsoft Visual C++ 2008 SP1 재배포 가능 패키지(x86)를 다운로드하십시오.
Visual C++ 2008 SP1 재배포 가능 패키지
Microsoft 업데이트에서 귀하의 PC를 위한 최신 업데이트를 찾아보십시오.
Microsoft 업데이트

개요

Microsoft Visual C++ 2008 재배포 가능 패키지(x86)는 Visual C++ 2008이 설치되지 않은 컴퓨터에서 Visual C++로 개발된 응용 프로그램을 실행하는 데 필요한 Visual C++ 라이브러리의 런타임 구성 요소를 설치합니다.

이 패키지는 CRT(C 런타임), 표준 C++, ATL, MFC, OpenMP 및 MSDIA 라이브러리의 런타임 구성 요소를 설치합니다. side-by-side 배포 모델을 지원하는 라이브러리(CRT, SCL, ATL, MFC, OpenMP)의 경우 side-by-side 어셈블리를 지원하는 Windows 운영 체제 버전의 네이티브 어셈블리 캐시(WinSxS 폴더)에 런타임 구성 요소가 설치됩니다. Visual C++ 응용 프로그램에 대해 지원되는 배포 방법을 보려면 여기를 클릭하십시오.

페이지 맨 위페이지 맨 위

시스템 요구 사항

지원 운영 체제: Windows 2000 Service Pack 4, Windows Server 2003, Windows Vista, Windows XP

  • 필요한 소프트웨어:

  • o Windows Installer 3.0 Windows Installer 3.1 이상이 권장됩니다.
  • 필요한 디스크 공간: 6MB(x86)

페이지 맨 위페이지 맨 위

설명

  1. 중요: 실행 중인 Windows의 버전에 대한 최신 서비스 팩 및 중요 업데이트가 설치되어 있는지 확인하십시오. 최신 보안 업데이트를 찾으려면 Windows Update를 방문하십시오.
  2. 다운로드를 시작하려면 이 페이지에서 다운로드 단추를 클릭합니다.
  3. 다음 작업 중 하나를 수행합니다.
    • 설치를 즉시 시작하려면 실행을 클릭합니다.
    • 다운로드 파일을 컴퓨터에 저장하고 나중에 설치하려면 저장을 클릭합니다.
    • 설치를 취소하려면 취소를 클릭합니다.

중요: 베타 1, 베타 2 또는 CTP(Community Technical Preview) 빌드와 같은 이전 시험판 버전의 Visual C++ 2008이나 Visual Studio 2008을 설치한 경우 최종 버전을 설치하기 전에 제어판의 프로그램 추가/제거를 통해 시험판 버전을 제거해야 합니다.

반응형
반응형

오늘은 메모리에 관해 좀 재밌는 내용을 설명하겠다.
레퍼런스 변수는 원본과 같은 메모리 공간을 가리킨다고 지난 포스트에 설명했다.
(기억이 나지 않는다면 [제2장] C++ 기본 : Reference 포스트를 참고하시라~)

레퍼런스 변수를 리턴값으로 쓰기 위해선 원본이 메모리상에 보존되어야 한다.
무슨 말인고 하니... 레퍼런스 변수가 가리킨 값(원본)이 사라진다면
레퍼런스 변수 역시 의미가 없어지기 때문이다.

일단 정상적으로 돌아가는 아래 예제를 살펴보자.

#include < iostream >
using namespace std;

int& increment(int &val) // 레퍼런스 변수로 원본을 받음
{
 val++;
 return val;
}

void main()
{
 int n = 10;
 int &ref = increment(n); // 파라미터로 레퍼런스 원본으 넘겨줌

 cout << "n : " << n << endl;
 cout << "ref: " << ref << endl;
}

자... 누구나 예상할 수 있듯이 결과는 11이 출력된다.
왜냐하면 원본 변수 int n 은 레퍼런스 변수가 사용되는 동안 유지되기 때문이다.
10의 값이 함수 안에서 ++ 연산을 통해서 11이 되었고 그것을 출력하였다.

그럼 당연히 반대의 경우도 살펴봐야되지 않겠는가?
지역변수를 레퍼런스 변수로 리턴하는 경우를 살펴보자.

#include < iostream >
using namespace std;

int& function()
{
 int val=10; // 지역변수가 원본이 되었다
 return val;
}

int& function2() // 임의로 추가한 두번 째 함수
{
 int val2=20;
 return val2;
}

void main()
{
 int &ref = function(); // 레퍼런스 변수 ref의 원본은 function()의 지역변수이다
 int &ref2 = function2();

 cout << ref << endl;
}

자... 무엇이 출력될 것 같은가? - 주의 : 원본 소스에서 function2()가 추가되었다 -
언뜻 보면 ref는 function()의 지역변수 int val = 10; 로부터 참조하였으니 10을 출력할 것 같다.

하지만 이 소스는 20을 출력한다!
ref는 분명 function()에서 값을 받았는데... function2()의 20이 출력된다.
우째 이런 일이...!

자 이제부터 이유를 알아보자.
main()이 실행되면 프로세서에 int ret와 int ret2에 대한 메모리를 할당한다 (총 8바이트)
예를들어 ret가 메모리 8000번에, ret2가 8004번에 할당되었다고 가정한다.

ㅁㅁㅁㅁ ㅁㅁㅁㅁ
(ret)   (ret2)

다음 function()이 실행되면 함수 내 지역 변수를 할당하기 위해서 추가적으로 4바이트를 할당한다
그럼 val은 메모리 8008번에 할당된다.

ㅁㅁㅁㅁ ㅁㅁㅁㅁ ㅁㅁㅁㅁ
(ret)   (ret2)   (val)

이제 ret는 레퍼런스 리턴으로 val의 위치를 참조하게 된다. 이때 가리키는 값은 10 이다.
(지금 cout으로 출력하면 10이 출력된다)

자~ 이제 함수 function()이 종료되고 지역번수 val이 차지하는 메모리공간 8008은 사라진다.

ㅁㅁㅁㅁ ㅁㅁㅁㅁ
(ret)   (ret2)

그리고 function2()가 실행되면서 val2의 메모리가 8008에 할당된다.

ㅁㅁㅁㅁ ㅁㅁㅁㅁ ㅁㅁㅁㅁ
(ret)   (ret2)   (val2)

중요한 점은 ret가 아직도 8008번을 가리키고 있다는 것이다!
val2의 값은 20이 대입되었으니 이제 ret의 값을 출력하면 20이 출력된다.

이러한 이유로 지역변수를 레퍼런스로 리턴할 경우 뜻하지 않은 결과를 초래할 수 있다.
이런 버그는 참 찾기 힘드니 절.대.로. 이렇게 코딩하지 말자!

반응형
반응형

추가자료 : http://blog.naver.com/leojesus?Redirect=Log&logNo=80045980042

아이오 교육센터의 기술자료는 다양한 서적/문서를 참고해서 교육센터 자체로 만든 자료입니다.

마음대로 퍼가셔도 되지만 꼭 출처를 밝혀 주시기 바랍니다.

float type에 대한 고찰

C 에서 실수 type은 아래의 2가지로 나눌수 있습니다.

float Type : 4byte 실수를 표현 하는 타입
double Type
: 8byte 실수를 표현 하는 타입

이 중에서 float type 에 대하여 역사적 배경에 기반하여 자세히 알아 보겠습니다.

다음 C언어 소스를 컴파일 후 실행 했을 때의 예상되는 값을 머리속으로 추측 해보고 실행하여 나오는 결과 값과 어떻게 차이가 나는 지 살펴 봅시다. 실행한 결과 값이 생각한 값과 같다면 실수의 내부 구조를 정확히 이해 하고 있으므로 더 이상 읽지 않으셔도 좋습니다.

#include <stdio.h>

int main()

{

float f = 0.1f;

float sum = 0.0f;

int i;

for( i=0 ; i<100000; i++ )

sum += f;

printf("%f\n", sum );

return 0;

}


프로그램의 결과는? 예상대로 0.1 * 100000 이므로 10000 이 나와야 정상이지만 무슨 이유 때문인지 1.5정도의 오차가 있는
9998.556641 로 출력 됩니다. 실수 표현이라 정확 할 것 같았지만 전혀 그렇지 않네요. 어떠한 이유 때문인지 실수의 내부구조를 살펴 봐야 할 것 같습니다.

정수와 다르게 실수는 표현하려는 정보가 두가지입니다. 양수/음수를 제외 하고도 말이지요. 그 두가지 정보는 바로 정수부와 소수부라는 것입니다.

3.14 라는 수가 있을 때 이수는

정수부 : 3
소수부 : 0.14

로 나눌 수 있습니다. 과연 메모리 4byte에 이수를 어떻게 표현 해야 가장 효율적일 까요?

초창기 방법은

고정 소수점 방식을 사용 했습니다. 고정 소수점 방식은 상위 16bit에는 정수부를 하위 16bit에는 소수부를 표현하는 방식이었죠.
예를 들어

10.25 라는 숫자가 있을 때

1. 이 수를 이진수로 바꾼다.
10.25 => 1010.01
2. 상위 16bit에는 1010을
3. 하위 16bit에는 01을 표현한다.

이를 그림으로 보자면 다음과 같습니다.

1 0 1 0 0 1

정수부 실수부
그림 1) 고정소수점 방식

위 그림을 보면 아주 합리적으로 보입니다. 정확히 메모리의 반을 정수부에 반은 실수부에 할당 하고 있으니까요.
하지만 이런식의 표현에서는 실수가 추구하는 두가지의 중요한 목적을 간과한 것입니다.

실수를 사용하는 목적은
- 매우 큰수를 표현하기 위해
- 매우 정밀한 숫자를 표현하기 위해

위 방식을 사용했을 경우 표현 범위는 양수만을 고려한다고 해도
범위 : 0 ~ 65536
정밀도 : 1/65536
볼 수 있다. 이는 매우 큰수도 아니고 매우 정밀한 수도 아니므로 실수의 사용 목적을 어느것도 만족하지 못합니다.


이런 표현 방법은 소수점이 중앙에 고정 되어 있다 하여 고정 소수점 방식이라 부릅니다.
그렇다면 매우 크면서도 정밀한 숫자를 표한하는 효율적인 방법은 무엇일까요?
이런 고민은 지금 우리 뿐만 아니라 우리의 선인들의 고민이기도 했습니다. 이에 유명한 국제 표준기관인 IEEE에서 아주 기발하면서도 흥미로운 제안을 합니다.

그 방법은 우리가 흔히 알고 있는 지수 표기법 이라는 것입니다.
이를 컴퓨터공학에서는 부동소수점이라고 부릅니다.

여기서 의문은 부동(
不動) 이라는 뜻은 움직이지 않는다는 것인데 고정가 무슨차이가 있느냐는 것이죠.
이때 부동이라는 뜻은 "움직이지 않는다" 가 아니고
부동(
浮動) 으로 아니부 가 아니고 뜰부를 써서 "떠서 움직이다" 라는 뜻입니다. 즉 소수점이 떠서 움직인다는 것이지요.

10.25 라는 숫자가 있을 때

1. 이 수를 이진수로 바꾼다.
10.25 => 1010.01

2. 소수점을 이동한다.
1010.01 => 0.101001 * 24
이를 그림으로 표현하면

0 0 0 0 0 0 1 0 0 1 0 1 0 0 1

부호 지수부 가수부

그림2) 부동 소수점 표현 방법

위에 방식으로 표현 하면

범위 : 2-128 - 2+127
정밀도 : 1/
8388608

로 표현 가능 하니 매우 큰 숫자도 표현 가능할뿐더러 매우 정밀한 숫자도 표현 가능 해졌다는 것입니다.


하지만 완벽할 것 같은 위방법도 두가지의 문제를 안고 있습니다. 바로

문제점 1 : 1010.01 => 0.101001 * 24 이런식의 이동은 어차피 왼쪽 1의 위치가 마지막 이동부분이므로 항상 0.1 형태로 시작한다. 그러므로 0.1까지 이동하지 말고 1.0 형태로 이동 하면 소수점의 쓸데 없는 1번의 이동을 막을수 있고 이렇게 하면 1bit의 낭비를 막을수 있다.

문제점 2 : 지수부에는 양수도 들어가야 하지만 음수도 들어가야 한다. 이때 음수를 표현하기위해 정수처럼 2의 보수를 사용한다면
sign bit를 따로 둔 실수에서는 중복처리라고 볼 수 있으므로 음수를 표현하기 위해 2의 보수 대신 127 바이어스 코드를 사용하여 문제를 해결한다.

10.25 라는 숫자가 있을 때

1. 이 수를 이진수로 바꾼다.
10.25 => 1010.01

2. 소수점을 이동한다.
1010.01 => 1.01001 * 23

이를 그림으로 표현하면

0 1 0 0 0 0 0 1 0 0 1 0 0 1

부호(s) 지수부(e) 가수부(m)

그림3) IEEE 부동 소수점 표준

메모리에는 위에 그림 처럼 저장 하고 다시 CPU로 로드 할 때는 다음 수식에 따라 로드 된다.

(-1)s * 1.m * 2(e-127)

위의 방식은 C언어 뿐만 아니라 C++, java, C# 등 모든 언어에서 사용하는 실수의 표준이므로 개발자하면 반드시 알아야 할 내용입니다.

실수의 내부 구조를 알아야 하는 이유

1. 정확한 실수의 내부구조를 알아서 논리적 에러 없는 코드 구현가능
2. double로도 표현 불가능한 초 정밀도 프로그램 (예. 항공우주, 유전자, 건축 등 ) 제작시 실수 타입 설계 가능

그렇다면 처음 논제로 들어 가서 0.1을 10만번 더했을 때 부동소수점으로 표현 했는데도 불구 하고 10000이라는 결과가 나오지 않았을까요?

그렇다면 위 지식을 배경으로 0.1의 내부 구조를 분석해 봅시다.

0.1 라는 숫자가 있을 때

1. 이 수를 이진수로 바꾼다. 소수점 이하를 바꿀때는 해당 소수점 이하의 수에 2를 계속 곱하고 연산의 결과인 정수를 위에서 아래로 쓴다.
0.1
0.2
0.4
0.8
1.6
1.2
0.4
0.8
1.6
1.2
....

0.1 => 0.0001100110011....
2. 소수점을 이동한다.
0.0001100110011.... => 1.100110011... * 2-4

이를 그림으로 표현하면

0 1 1 1 1 1 0 1 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0

부호(s) 지수부(e) 가수부(m)

그림3) 0.1의 IEEE 부동 소수점 표준

즉 , 그림에서 알수 있듯이 0.1이 10진수에서는 유한소수이지만 2진수를 변환하게 되면 2의 배승단위가 아니므로 무한소수가 된다. 아무리 가수부를 23자리나 표현한다 해도 유효숫자가 잘려 나가기 때문에 많은 회전수를 돌리면 오차가 발생하는 것이다.

실수 프로램시 가이드 라인)

회전수가 많거나 정밀한 수 표현시 절대 float를 사용하지 말고 double을 사용하라. double도 초정밀도 프로그램에는 적절치 않으므로 초정밀도 관련 프로그램은 type을 생성하여 사용하라.

따라서 처음 프로그램의 올바른 사용예는 다음과 같다.

#include <stdio.h>

int main()

{

double f = 0.1; // 0.1 상수는 double로 해석된다.

double sum = 0.0;

int i;

for( i=0 ; i<100000; i++ )

sum += f;

printf("%lf\n", sum ); // printf 포메팅은 double인 경우 lf사용

return 0;

}

결론 : 실수는 부동 소수점을 사용하고 float(32bit: s=>1, e=>8, m=>23), double(64bit: s=>1, e=>11, m=>52)의 내부구조를 가지고 있습니다. 간단한 실수 프로그램에는 float을 사용하고 정밀한 프로그램에서는 반드시 double을 초정밀도 프로그램에서는 type을 생성하여 사용해야 합니다.

이로써 여러분은 정수와 실수를 정확하게 알게 되셨으므로 c언어의 기본인 type을 마스터 하게 되었습니다.
다음에 진행될 연산자와 제어 구조도 계속 공부 하세요...

출처 : C기본부터 고급까지 C 교육의 모든 것. 아이오 교육센터(www.ioacademy.co.kr)

반응형
반응형

.ini 클래스

한번 열고 read, write 를 자유롭게 사용 할 수 있도록 인터넷에 떠도는 것을 수정했다

반응형
반응형

CString -> char*

http://cafe.naver.com/woosongbitcafe/123

<문자열변환>
// char를 TCHAR로 변환
mbstowcs(TCHAR* str, char* str2, 256);

// TCHAR를 char로 변환
wcstombs((char* str, TCHAR* str2, 256);

// 문자열을 정수로 변환
int Num = _ttoi(TCHAR* str);

// 정수를 문자열로 변환
TCHAR buf[256];
int Num;
_itow(Num, buf, 10); // 10은 10진법을 의미

// Cstring 에서 wstring으로 변환
CString cstr;
wstring wstr(_T(""));
wstr += cstr.GetString();

// wstring에서 CString으로 변환
CString cstr;
wstring wstr(_T(""));
cstr = wstr.c_str(); 

반응형
반응형

http://kimbeast.blog.me/60036942254



라이브러리 만들던중 문득 나중에 UNICODE로 만들게 되면.? 이라는 의문에 unicode출력 테스트를 하게 되었다.

환경 (gcc 4.1.x, opensuse 10.2)


#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <iostream>

using namespace std;
int main()
{
setlocale(LC_ALL, "ko_KR.utf8");

wchar_t *wstring3 = L"한글Alpha";

wcout << wstring3 << endl;

return 0;
}


위와 같이 하니.잘 나왔다.
그래서 라이브러리에 집어넣고 출력을 봤더니만. .아무것도 안 나오는 현상이 발생하였다.
원인이 뭘까..고민하던 차에 아주 황당한 결과를 보게 되었다.


#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <iostream>

using namespace std;
int main()
{
setlocale(LC_ALL, "ko_KR.utf8");

wchar_t *wstring3 = L"한글Alpha";

cout << wstring3 << endl;
wcout << wstring3 << endl;


return 0;
}


위와 같이 한다면..출력결과는???

cout 의 결과만 나온다.
wcout 의 결과는 엉망으로 나온다.

다시 반대로..


#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <iostream>

using namespace std;
int main()
{
setlocale(LC_ALL, "ko_KR.utf8");

wchar_t *wstring3 = L"한글Alpha";

wcout << wstring3 << endl;
cout << wstring3 << endl;

return 0;
}


wcout 의 결과만 나온다.
cout 의 결과는안나온다.

즉 라이브러리안에 있던 cout 문장이 출력에 영향을 줘서 unicode 출력이 되지 않는 것이었다.

결론적으로 유니코드 base 로 만들려면 cout을 절대 사용하지 말란 말이 된다.
이게 어디 쉬운일인가? 잘 통제되지 않으면.그냥 출력한다고 cout넣을텐데 말이다.
오묘한 세계다...

반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

.ini 파일 클래스  (0) 2012.10.31
문자열 변환  (0) 2012.10.31
Timer - getElapsedMilliSecond  (0) 2012.10.31
C 언어에서 파일을 여는 함수 입니다. W/R 모드  (0) 2012.10.31
함수포인터  (0) 2012.10.31
반응형

Timer

전산 2004/03/02 14:40
타이머 문제로 골몰하다 멀티미디어 라이브러리의 바이너리를 뒤적거리기로 했다. 한참동안 winmm.lib 의 네이티브 코드를 뒤지다가 재미난 것을 한가지 발견. 일단 해당 부분을 뜯어 간단히 인라인 어셈으로 옮겨 보았다.

inline unsigned __int64 getMisteryCount()
{
__asm
{
mov edx,dword ptr ds:[7FFE000Ch]
mov eax,dword ptr ds:[7FFE0008h]
}
}


아무런 문서도 없이 바이너리를 뒤지다 얻어낸 정보인지라 저 주소가 무엇을 의미하는지는 나도 모른다. 구글에도 없는것을 보니 완전한 un-documented feature 같다. 어쨌든 저 주소에는 1/10000 초 단위의 시간이 저장되어 있다. 물론 아래와 같이 10000 을 나누어 milli-second 단위로 변환해서 사용하는것도 가능하다.

inline unsigned int getElapsedMilliSecond()
{
return static_cast< unsigned int >( getMisteryCount()/10000 );
}


mov 를 두번 호출할 뿐인지라 속도는 경이적이다. 빠르지만 최악의 정밀도를 보여주는 GetTickCount() 보다도 4배정도 빠르다. 아무일도 하지 않고 루프만 돈 noproc() 에 거의 근접한다.



getMisteryCount() 와 getElapsedMilliSecond() 의 속도차이의 이유는 64-bit div 연산에 있다. 32-bit 프로세서에서 __int64 나누기를 하려면 수십개의 인스트럭션이 필요하다.

테스트 결과 getMisteryCount() 는 Win95/98/Me 등에선 동작하지 않는다. XP , 2000, 2003 등 NT 커널 기반 OS 에서만 돌아가는 듯 하다. 어쨌건 퍼포먼스 카운트용으로 이보다 더 좋은 타이머는 없을 듯 하다. 

반응형
반응형

C 언어에서 파일을 여는 함수 입니다.

#include <stdio.h>

FILE *fopen(char *fname, char *mode)

char *fname : 열고자 하는 파일명

char *mode : 파일여는 방식

반환값 : 성공하면 파일 포인터, 실패하면 NULL을 반환한다.

파일 열기 모드

"r" : 읽기. 이미 존재하는 파일을 연다.

"w" : 쓰기. 파일이 없으면 생성하고 있으면 내용을 지운다.

"a" : 추가. 파일이 없으면 생성하고 있으면 파일의 현재 위치를 끝에 위치시킨다.

"r+" : 읽기와쓰기. 이미 존재하는 파일을 연다.

"w+" : 읽기와쓰기. 파일이 없으면 생성하고 있으면 내용을 지운다.

"a+" : 읽기와쓰기. 파일이 없으면 생성하고 있으면 파일의 현재 위치를 끝에 위치시킨다.

추가적으로 위의 모드에 "r+b", "rt"등과 같이 "b", "t" 를 추가하여

"b" : 이진모드, "t" : 텍스트 모드로 열 수 있다.

사용하지 않으면 기본 값은 텍스트 모드이다.

ex)

fp = fopen( url, "rt" );

반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

wchar_t or char .. (유니코드 출력문제)  (0) 2012.10.31
Timer - getElapsedMilliSecond  (0) 2012.10.31
함수포인터  (0) 2012.10.31
GetAsyncKeyState  (0) 2012.10.31
GetPrivateProfileStringA  (0) 2012.10.31
반응형

<MSXML> VC++에서 MSXML 사용법 | XML
오랜만에 다시 강의를 시작합니다. *^^*

MSXML의 개요만 설명하고, VB와 VC++에서 사용법 예제를 지난 강의에 넣었습니다.

지금 다시보니 VB는 잘 되는 것 같은데, VC++에서는 dll의 포함여부에 따라 제대로 동작하기도 하고, 안하기도 하는 것 같습니다.

그래서 이 부분을 한번 정리해 보려고 합니다.


실제로 MSXML의 쓰임새는 VB가 더 많을 것이라고 생각합니다. ASP를 이용해 웹에서도 쓸 수 있고, 자바스크립트에서도 활용하고 있으니까요.

그러나 C/S 프로그램이나 XML관련 애플리케이션을 VC++로 개발하는 경우, 처음 msxml을 사용하려면 어려움을 겪는 분들이 간혹 있습니다.

특히, 저처럼 COM에 대한 개념이 생소한 분들이 그렇지요..

CoInitialze나 CoCreateInstance.. 이런 함수들이 튀어 나오면 그 순간.. 아주 답답해지죠..

실은 저도 COM에 대해서는 잘 모릅니다. 물론 위의 함수의 정확한 의미도 설명할 정도의 수준은 아니고요..

BUT, MSXML을 사용하기 위한 방법에 대해서는 한번 설명해 보려고 합니다.

VB는 MSDN에 나와있는 예제만 참고해도 잘 활용할 수는 있으니.. VC++을 먼저 알아보죠..

음.. 데브피아나 이런데 가면 좋은 MSXML을 위한 wrapper 클래스들이 많이 있을 테니.. 그걸 사용하셔도 되구요..
(저도 이 강의를 끝내면서 library 하나 만들어 볼까 합니다. 나름대로 편리하게 구성해서.. 쩝~ 생각만... 할려나.. 에궁)

필요작업

일단, VC++을 작업하기 위해서 필요한 것부터 간단히 정리해 보죠..

1. 플랫폼 SDK라는 것 들어봤죠.. 이걸 먼저 설치해야 합니다.
요즘 Visual C++로 개발하는데, 이게 없으면 안되는 게 너무 많더군요.. 한번 설치해 두는 것도 괜찮을 것 같네요..

MS의 사이트 주소는 맨날 바뀌니.. 그렇더라도 현재 다운로드 할 수 있는 주소를 하나 적어놓을께요..

http://www.microsoft.com/msdownload/platformsdk/sdkupdate/default.htm

플랫폼 SDK를 설치하는 이유는 여기에 <msxml2.h>라는 헤더 파일이 있기 때문입니다.

2. VC++의 환경설정에서 플랫폼 SDK를 지정해 줘야 합니다.

뭐.. 대부분은 잘 아시겠지만, 혹여라도 잘 모르시는 분이 계실까봐~~

[Tools] - [Options] - [Directories]로 가셔서..

Inclue files 와 Library files에 플랫폼 SDK의 Include와 Lib를 지정하시면 됩니다.

이때, 플랫폼 SDK의 Include와 Lib를 가장 위로 올려놓는게 좋기는 합니다.

3. MFC를 사용하는 프로그램이라면, stdafx.h란 파일이 존재할 겁니다.

여기에 다음과 같은 소스를 아래부분에 추가합니다.

만약 stdafx.h가 없다면.. MSXML을 사용할 헤더나 소스 파일에 이걸 추가하면 됩니다.

#include <MSXML2.H>
#pragma comment (lib, "msxml2.lib")

자.. 이렇게 하면 이제 MSXML을 사용할 준비가 된 것입니다. 그럼.. 시작해 볼까요..

MSXML 이해

MSXML 도움말을 살펴보면 Node나 Attribute를 선언할 때, 변수명이 특이합니다.

이것은 MSXML자체가 DOM 스펙을 따르면서 COM형태로 개발되었기 때문인것 같습니다.

IXMLDOMNode, IXMLDOMAttribute, IXMLDOMDocument

뭔가 공통점이 있죠?

I - 인터페이스를 의미하는 것 같네요
XML - XML이라는 거죠.. 당연한 거죠.. 쩝~
DOM - DOM 스펙을 따른다는 이야기고..
나머지 - 각각의 의미를 나타내고 있죠..

그럼 자주 사용하는 몇가지 변수명만 설명할께요..

IXMLDOMDocument - XML 문서의 최상위 객체입니다. 다른 XML 객체는 여기에서부터 파생된다고 볼 수 있습니다.
IXMLDOMNode - XML의 각각의 Node를 가리키는 객체입니다. DOM의 가장 기본 요소입니다.
IXMLDOMAttribute - Node의 속성을 나타내는 객체입니다.
IXMLDOMElement - XML의 요소(Element)를 나타내는 객체입니다. 실제로 사용할 때는 Node를 더 많이 쓰는 것 같습니다. 요것 보다는
IXMLDOMNodeList - 주로 자식 노드의 리스트, XPath를 이용해 검색한 노드들의 리스트를 가리는 겁니다.
IXMLDOMNamedNodeMap - 주로 속성들의 리스트를 나타낼 때 사용합니다. NodeList와의 구분은 순서가 중요하냐의 여부에 달려있죠..

이외에도 IXMLDOMComment, IXMLDOMProcessInstruction.. 등이 있는데..
앞의 강좌인 XML 기본 강좌를 읽어보신 분은 대략 이해할 수 있을 거라 생각합니다.

이제 본격적으로 코딩을 해봅시다.

프로젝트는 알아서 만드시고.. 주요부분만 한번 해보도록 하겠습니다.

소스를 간결하게 하기 위해서 에러 처리부분은 생략했습니다.
에러가 포함된 소스는 자료실에서 다운로드 받으시면 됩니다.

그리고 여기에는 COM관련 함수들이 많이 나옵니다.
제가 COM을 잘 모르기 때문에 자세한 설명을 할 수가 없습니다. 혹, COM에 대해 잘 아시는 분이 계시면 코멘트를 달아서 설명해 주시면 고맙겠습니다.

MSXML을 사용하기 위해서 먼저 CoInitialize(NULL)를 실행해야 합니다.
그리고 메모리 해제를 위해 끝나면 CoUninitialize()를 하면 되겠죠..

이건 COM 객체를 사용하기 위해 기본적으로 사용하는 것 같습니다.

CoInitialize(NULL);

... // 이 사이에서 msxml을 활용하면 됩니다.

CoUninitialize();

CoInitialize나 CoUninitialize는 만약 클래스로 만든다면 생성자나 소멸자에 넣어도 되겠죠..

그럼 중간에서 XML을 사용하기 위해서는 XML의 기본 객체를 만들어야겠죠.. 바로 IXMLDOMDocument 입니다.

IXMLDOMDocument* m_pXml;

그리고 나서 인스턴스를 생성합니다.

CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&m_pXml);

이렇게 하면 xml 객체를 활용할 수 있게 됩니다. 바로 m_pXml을 통해서죠...

CoCreateInstance를 살펴보면,
Co - COM 관련 함수라는 의미 같은데요.. Cowork인가? 쩝~~ 암튼 그렇구요..
CreateInstance - 인스턴스를 생성하는 것이죠.. 객체지향 프로그램에서는 객체나 클래스, 인터페이스를 사용할 수 있도록 하기 위해서는 그것의 인스턴스를 생성해야 하죠..

여기까지 소스를 종합해보면 다음과 같습니다.

CoInitialize(NULL);

IXMLDOMDocument* m_pXml;
CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&m_pXml);

// 필요한 작업을 수행합니다.

CoUninitialize();


그럼 이번 강좌에서는 XML 문서를 가져오는 것만 해보도록 하죠..
XML 문서는 XML 기초강좌에서 사용한 것을 활용하겠습니다.

MSDN에서 IXMLDOMDocument의 load 함수를 살펴보겠슴다.
XML 문서를 로드할 경우, 파일에서 로드하는 경우와 XML 문자열을 로드하는 경우가 있습니다.
일반적으로 XML 문자열을 로드하기 보다는 파일을 로드하는 경우가 많다고 생각되므로 파일 로드에 대해서 자세히 설명하도록 할께요.

HRESULT load(
VARIANT varXmlSource,
VARIANT_BOOL* varIsSuccessful
);

함수 원형은 위와 같습니다.
VARIANT라는 것이 나오는데요.. 이건 각각의 형별로 union으로 선언한 것이라고 생각하면됩니다.
그러니까 "가변형"이라고 생각하면 되겠네요..

VARIANT varXmlSource는 XML 파일명을 가리키는 것이고요
VARIANT_BOOL* varIsSuccessful 성공 여부를 가리키는 것입니다.

그럼 한번 적용해 볼까요?

CoInitialize(NULL);

IXMLDOMDocument* m_pXml;
CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&m_pXml);

// 파일명을 지정합니다.
variant_t file("test.xml");
VARIANT_BOOL bLoad;

// 파일을 오픈합니다.
HRESULT hr;
hr = m_pXml->load(file, bLoad);
if (FAILED(hr)) return;

if (bLoad) {
// 성공
}

CoUninitialize();

이런형태로 사용할 수 있습니다.
VARIANT 구조체에 맞추어 문자열을 변환하기 위해서 variant_t를 사용했네요..
이걸 사용하기 위해서는

#include <comdef.h>를 포함해야 합니다.

그리고 HRESULT라는 리턴값을 사용했습니다.

typedef LONG HRESULT;

MSDN에 보면 이렇게 선언되어 있는데요. 결과를 알려주는 것입니다.
정확하게 S_OK, S_FALSE, S_INVALIDARG로 리턴이 되는데..

성공과 실패여부를 확인하기 위해
SUCCEEDED(hr) 또는 FAILED(hr) 이렇게 사용할 수 있네요.

자.. 이번에는 읽은 XML 문서의 내용을 화면에 출력하는 부분을 해보도록 하겠습니다.

HRESULT get_xml(
BSTR *xmlstring);


여기에서는 BSTR을 사용하는 방법을 잘 살펴보시면 될 것 같습니다.

if (bLoad) {
BSTR bstr;
CString strValue;

// XML 내용 가져오기
m_pXml->get_xml(&bstr);

// BSTR 변환
USES_CONVERSION;
strValue.Format("%s", W2A(bstr));
SysFreeString(bstr);

// 화면 출력
printf("%s\n", strValue);
}

USES_CONVERSION이나 W2A와 같은 것을 사용하기 위해서는

#include <atlconv.h>를 포함해야 함니다.

BSTR을 CString으로 변경하기 위해 위와 같은 방식을 사용했네요..
그냥 Casting을 해도 결과를 보는데는 문제가 없지만, 메모리 누수를 막기위해 저렇게 사용했습니다.

물론 위와 같은 방식이 아니라 VB와 같이 쉽게 VC++을 사용하는 방법이 있기는 합니다만,
전 이방식으로 쭉 설명할 계획입니다.

경우에 따라서는 필요하기 때문이죠.. C++에 대해서 잘 모르시는 분들은 좀 어렵게 느꼈을 것 같네요..
그래도 한번 따라해 보세요.. 한번의 실습이 열번 읽는 것보다 훨씬 나으니까요..

전체 소스를 한번 정리하겠습니다. 자료실에도 이와 관련된 소스를 넣어둘께요..

그럼.. 좋은 하루 되세요~~ 2005/03/29 미니


if (!SUCCEEDED(CoInitialize(NULL))) return 0;

// Instance 생성
IXMLDOMDocument* m_pXml;
if (!SUCCEEDED(CoCreateInstance(CLSID_DOMDocument, NULL,
CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&m_pXml))) {
CoUninitialize();
return 0;
}

// XML 파일 읽어오기
variant_t file("test.xml");
VARIANT_BOOL bLoad;
HRESULT hr;

hr = m_pXml->load(file, &bLoad);
if (FAILED(hr)) {
CoUninitialize();
return 0;
}

// XML 내용 출력
if (bLoad) {
BSTR bstr;
CString strValue;

// XML 내용 가져오기
hr = m_pXml->get_xml(&bstr);
if (SUCCEEDED(hr)) {
// BSTR 변환
USES_CONVERSION;
strValue.Format("%s", W2A(bstr));
SysFreeString(bstr);

// 화면 출력
printf("%s\n", strValue);
}
}

CoUninitialize();

반응형
반응형

1. 프로젝트 속성 --> C/C++ --> 일반-->
디버깅 정보형식 에서 --> 프로그램 데이타 베이스를 선택한다. ( 원래는 사용 않함 )
2. 프로젝트 속성 --> 링커 --> 디버깅-->
디버깅 정보생성 --> 예/DEBUG 선택한다. ( 원래는 아니오 )
3. 솔류션 정리
4.솔루션 다시빌드

반응형
반응형

  1. void main()
  2. {
  3. int input;
  4. printf("입력 : ");
  5. scanf("%d", &input);
  6. switch(a)
  7. {
  8. case 1:
  9. printf("1번\n");
  10. break;
  11. case 2:
  12. printf("1번\n");
  13. break;
  14. case 3:
  15. printf("1번\n");
  16. break;
  17. case 4:
  18. printf("1번\n");
  19. break;
  20. }
  21. }


위와같은 코드는 유지보수에 있어서 문제가 상당히 큽니다.
case 부분의 실행부분이 커질수록 가독성이 급격히 하락합니다.
그래서 보통 어느정도 생각이 조금 박힌 사람은 다음과 같은 코드를 사용합니다.

  1. void func_one()
  2. {
  3. printf("1번\n");
  4. }
  5. void func_two()
  6. {
  7. printf("2번\n");
  8. }
  9. void func_three()
  10. {
  11. printf("3번\n");
  12. }
  13. void func_four()
  14. {
  15. printf("4번\n");
  16. }
  17. void main()
  18. {
  19. int input;
  20. printf("입력 : ");
  21. scanf("%d", &input);
  22. switch(a)
  23. {
  24. case 1:
  25. func_one();
  26. break;
  27. case 2:
  28. func_two();
  29. break;
  30. case 3:
  31. func_three();
  32. break;
  33. case 4:
  34. func_four();
  35. break;
  36. }
  37. }


프로그램의 구조화를 적용해서 그나마 조금 낫게 만들었네요..
하지만 switch-case 가 남아있어 case 의 몸통이 커질 가능성은 아직 남아있네요..
저 switch-case 를 어떻게 없앨 수 있는가..
여기서 사용할 수 있는게 "함수 포인터 배열" 입니다.

여기서 잠깐
함수 포인터란?
C/C++ 컴파일러는 소스코드를 컴파일할 때, 심벌테이블을 만듭니다(여기까진 다 아시져?)
이 심벌테이블에 들어가는건 변수의 주소 뿐 아니라 함수의 실행코드의 시작위치까지 저장이 되는데요,
(그 단적인 예로 사용자 정의 함수의 이름과 변수의 이름을 똑같이 쓰면 말도 안되는 동작이 가끔씩 나옵니다.)
(변수명 - 주소), (함수명-주소) 의 쌍으로 저장을 합니다. 이러한 특징 때문에 함수의 이름을 포인터처럼 사용할 수가 있는것이져..
말이 너무 길었네요 우선 함수포인터의 선언형태부터 보져

void(*func_ptr)();
와 같은 형식으로 선언합니다.
연산자 우선순위상 다음과 같은 선언을 하게되면
void *func_ptr();
void * 를 리턴하는 함수를 선언하는걸로 인식해서 컴파일러는 에러를 뱉어냅니다.
따라서 () 로 우선순위를 잡아주는거죠...

간단한 함수포인터 사용 예제입니다.

  1. void (*func_ptr)();
  2. void func_one()
  3. {
  4. printf("1번\n");
  5. }
  6. void main()
  7. {
  8. func_ptr = func_one;
  9. (*func_ptr)(); //func_one() 함수 실행
  10. }


위에서 말씀드린것 처럼 함수의 이름이 포인터로 사용되어 호출하는 모습입니다.

이걸 좀더 응용해서 switch-case 를 없애고 가독성을 확보해보도록 하겠습니다.

  1. void (*func_ptr[4])();
  2. void func_one()
  3. {
  4. printf("1번\n");
  5. }
  6. void func_two()
  7. {
  8. printf("2번\n");
  9. }
  10. void func_three()
  11. {
  12. printf("3번\n");
  13. }
  14. void func_four()
  15. {
  16. printf("4번\n");
  17. }
  18. void main()
  19. {
  20. int input;
  21. func_ptr[0] = func_one;
  22. func_ptr[1] = func_two;
  23. func_ptr[2] = func_three;
  24. func_ptr[3] = func_four;
  25. printf("입력 : ");
  26. scanf("%d", &input);
  27. (*func_ptr[input-1])();
  28. }


switch-case 가 사라져서 아주 깔끔한 형태가 되었네요
어때요? 참 쉽져? ㅋㅋㅋ

사족을 달자면, 현재 MFC 에서는 위와 같은 방법으로 메세지를 처리하고 있습니다. 

반응형
반응형

 dll 로 만든 헤더를 A에서 dll 함수를 호출하면서 인자로 A 의 멤버 포인터를 넘긴다음

dll 안에서 A 의 함수를 접근하려할때 오류가 난다

원인 : dll 안에서 A 로 접근 할 수 있는 함수의 주소를 알 수 없다

+ 헤더파일에 선언된 함수 즉 inline 함수는 dllexport 할 필요가 없다

반응형
반응형

어디선가 퍼온글

GetAsyncKeyState() 함수를 호출할 경우
키가 눌려져 있으면 최상위 비트가 1인 short형(2Byte) 리턴값을 리턴합니다
키가 눌려져 있지않으면 최상위 비트가 1이 아닌 어떠한 값을 리턴합니다
그래서 0x8000(최상위 비트가 1)과 & 연산을 해주면
이 키가 눌려져 있는 상태인지 아닌지 알 수 있습니다


예) if ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 )

반응형
반응형

GPG 사이트 에서..


D3DXVec3TransformNormalD3DXVec3TransformCoord 의 차이에 대해서

간단히 말씀드리도록 하겠습니다.

단순하게 말하자면 D3DXVec3TransformNormal 은 벡터를 변환하는 것이고

D3DXVec3TransformCoord 는 위치를 변환하는 것입니다.

벡터와 행렬을 곱하기 위해서는 행과 열이 같은 차수여야 한다는 것을

알고 계실 것입니다. 예를 들어서

[x, y, z] 라는 3차원 벡터가 존재하는데 D3D 에서는 4x4 행렬을 곱하게 됩니다.

이러한 벡터를 행렬과 연산을 할 때는 차수를 맞춰줘야지 곱할 수 있게 됩니다.

즉 3차 벡터를 마치 1x4 행렬 혹은 4x1 행렬인 것처럼 만들어 주어야

4x4 행렬과 곱하는 것이 가능하다는 것이죠.

D3DXVec3TransformNormal 은 [x, y, z] 를 [x, y, z, 0] 으로 만들고

D3DXVec3TransformCoord 는 [x, y, z] 를 [x, y, z, 1] 로 만들어서 연산을 합니다.

물론 결과는 [Rx, Ry, Rz] 형태인 3차원 벡터로 반환하게 되죠.

그런데 마지막에 0 이 붙느냐 1 이 붙느냐가 어떤 의미인지 궁금하실 것 같습니다.

Direct3D 에서 사용하는 행렬의 마지막 열 성분은 위치 변환(Translation)을 의미하고 있습니다.

즉 다음과 같은 형태를 띠고 있습니다.

| _11 _12 _13 Tx |
| _21 _22 _23 Ty |
| _31 _32 _33 Tz |
| _41 _42 _43 1 |

여기에서 _xx 식으로 표기한 부분은 회전과 크기 변환에 대한 부분입니다.

이것을 D3DXVec3TransformNormal 을 위해 만들어낸 행렬과 연산을 해 봅시다.

| _11 _12 _13 Tx | | x |
| _21 _22 _23 Ty | | y |
| _31 _32 _33 Tz | | z |
| _41 _42 _43 1 | | 0 |

이것을 연산할 때 Tx, Ty, Tz 요소는 0 과 곱해지게 된다는 것을 알 수 있습니다. 즉 다시 말해 변환 행렬의 이동 성분이 반영되지 않는다는 것입니다.

다음으로 D3DXVec3TransformCoord 를 위해 만들어낸 행렬과 연산을 해 봅시다.

| _11 _12 _13 Tx | | x |
| _21 _22 _23 Ty | | y |
| _31 _32 _33 Tz | | z |
| _41 _42 _43 1 | | 1 |

Tx, Ty, Tz 요소들은 1과 곱해지기 때문에 변환 행렬의 이동 성분이 반영되고 있음을 알 수 있습니다.

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

이제 질문하신 부분에 대해서 이야기해 보도록 하겠습니다.

D3DXVECTOR3 vA, vB;
D3DXMATRIX mTransform;

vB = D3DXVECTOR3(1, 1, 1);
D3DXMatrixIdentity(&mTransform);

D3DXVec3TransformNormal(&vA, &vB, &mTransform);

이라는 식으로 했을 때 이것이 어떠한 결과를 낳으며

행렬의 역할은 무엇인가에 대해서 질문하신 것 같습니다.

이 함수는 기본적으로 위에서 설명했듯이 vB 가 위치를 나타내는 점이 아니라

방향과 크기를 나타내는 벡터라는 가정을 하고 있습니다.

또한 변환 행렬의 이동 성분은 무시하기 때문에 위의 함수가 최종적으로 반환하는 vA 는

로컬 공간에서의 벡터에 회전 및 스케일 변환을 적용했을 때의 월드 공간에서의 벡터를 의미하게 됩니다.

이러한 연산을 사용하는 것은 로컬에서의 법선 벡터가 변환 이후

월드에서 어떤 방향과 크기를 가지는 지를 알아내야 하는 경우입니다.

하지만 만약 이것이 점이라 가정하고 로컬 공간에서 mTransform 을 적용한 것 만큼

월드 공간으로 이동시키고자 한다면

D3DXVec3TransformNormal 이 아니라 D3DXVec3TransformCoord 를 사용해 줘야 합니다.

그래야지 이동 성분이 평가가 되어서 월드 공간에서의 위치를 얻을 수 있겠죠.

답변이 제대로 되었는지 모르겠지만... 도움이 되었으면 좋겠네요.

즐플하세요.

반응형
반응형

안녕하세요 단편으로는 처음 쓰는 것 같습니다.

이 단락으로는 조금 어색할 지도 모르겠지만 윈도우 에서 지원하는 INI 파일의 입출력 입니다.

그래서 API 의 메뉴에 쓰게 됩니다.

게임이나 기타 유틸등을 만들때 상당히 파일 입출력에 대해 고민을 하게 됩니다.

그래서 이번에 이렇게 글을 올리게 됩니다.

아직 모르시는 분이 있을지? 도 몰라서 ^^;

여기에서 쓰이는 함수에는 여러가지가 있습니다.

그 중에서도 가장 많이 쓰이는 Int 형과 String 형에 대해 알아보겠습니다.

struct 와 Section 이 있지만 Section 은 거의 쓰이지 않는 편 이고 Struct 와 같은 경우는 바이너리로 쓰는게 더 편하기 때문에^^; 생략합니다.

미리 알아 두어야 할 점은 쓰기에는 STRING 으로 INT 를 그냥 쓸수 있어서 INT 형을 쓰는 함수는 없습니다.

그리고 INI 파일에 쓰이는 형식이 있습니다.

[TITLE] //단락의 시작

NAME = JAEJIN //변수 = 값

이런 식 으로 되어 있습니다. 단락의 시작으로 부터 그 아래의 값들을 찾는 형식으로 되어 있습니다. 단락은 대 괄호로 나타내구요^^

첫번째로 쓰기에 쓰이는 함수는

WritePrivateProfileString 입니다.

WINBASEAPI
BOOL
WINAPI
WritePrivateProfileStringA(
IN LPCSTR lpAppName,
IN LPCSTR lpKeyName,
IN LPCSTR lpString,
IN LPCSTR lpFileName
);

선언에는 이렇게 되어 잇습니다.

lpAppName 에는 단락의 시작 의 이름이 들어 갈 것이고,

lpKeyName 은 변수가 들어 갑니다.

lpString 은 값이 들어 갈 것이고

lpFileName 에는 파일의 경로와 이름이 들어갑니다.

#include <windows.h>


int _tmain(int argc, _TCHAR* argv[])
{
::WritePrivateProfileString( "TITLE", "STRING_KEY", "NAME", "./TEST.INI" );
::WritePrivateProfileString( "TITLE", "NUMBER_KEY", "1024", "./TEST.INI" );
}

자 이렇게 쓴다면 INI 파일에는

[TITLE]
STRING_KEY=NAME
NUMBER_KEY=1024

이렇게 기록이 될 것 입니다.

눈으로 보이기 때문에 상당히 편안한 점이 있습니다.

자 그다음은 읽기 차례 입니다. 위에서는 String 으로 썼지만 NUMBER_KEY 는 INT 형 입니다.

그래서 타입 캐스팅을 하지 않기 위해 INT 형을 읽는 함수가 제공이 됩니다.

여기서 제공 되는 함수는

GetPrivateProfileString

GetPrivateProfileInt

2가지 입니다.

WINBASEAPI
DWORD
WINAPI
GetPrivateProfileStringA(
IN LPCSTR lpAppName,
IN LPCSTR lpKeyName,
IN LPCSTR lpDefault,
OUT LPSTR lpReturnedString,
IN DWORD nSize,
IN LPCSTR lpFileName
);

일단 String 의 원형은 이렇게 됩니다.

lpAppName 은 단락의 시작을 쓰시면 됩니다.

lpKeyName 은 변수가 될 것 이고,

lpDefault 는 만약 저 변수가 없다면 기본으로 읽을 대체문자열을 나타냅니다.

lpReturnString 은 값을 받을 버퍼를 나타내고,

nSize 는 버퍼의 사이즈를 입력 하면 됩니다.

lpFileName 은 파일을 읽을 경로와 이름을 쓰시면 됩니다.

WINBASEAPI
UINT
WINAPI
GetPrivateProfileIntA(
IN LPCSTR lpAppName,
IN LPCSTR lpKeyName,
IN INT nDefault,
IN LPCSTR lpFileName
);

Int 의 원형 입니다.

lpAppName 은 단락의 시작을 쓰시면 되고

lpKeyName 은 변수입니다.

nDefault 는 값을 못찾으면 대체로 쓸 숫자를 적고,

lpFileName 은 파일의 경로와 이름을 쓰시면 됩니다.

#include <windows.h>


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

char szBuffer[ 256 ] = {0, };
::GetPrivateProfileString( "TITLE", "STRING_KEY", "Unknown", szBuffer, 256, "./TEST.INI" );
int iResult = ::GetPrivateProfileInt( "TITLE", "NUMBER_KEY", 0, "./TEST.INI" );

}

아까 썼던 것을 다시 읽는 소스 입니다.

아까 TITLE 이란 단락으로 STRING_KEY 에 NAME 이란 값을 입력 했습니다. 만약 이 단락을 찾지 못하면 Unknown 이라는 STRING 이

szBuffer 에 찰 것이고, 찾았다면 NAME 이라는 값이 Buffer 에 들어옵니다.

아래 것도 마찬가지로 TITLE 이라는 단락으로 NUMBER_KEY 에 1024 를 썼습니다. 찾지 못하면 0 이 리턴되고 찾았다면 1024가 리턴 될 것 입니다.

소스도 첨부 하겠습니다.

이제는 txt 로 노가다 뛰시는 분들은 텍스트 노가다를 줄여 주시고. 텍스트는 그리고 위험하게 버퍼가 어긋나면

바로 뻑이 납니다.

그래서 저는 이걸 자주 씁니다^^

주의 할 점은 마지막 인자에 FileName 을 쓰는데 앞에 ./ 를 꼭 붇여서 CurrentFolder 라고 명시를 하셔야 합니다.

하지 않으면 못 읽습니다.

또 유용한 점이 있다면 게임을 만드는 개발자 입니다.

유저의 해상도를 파일에 기록하여 런처를 만든다고 합시다. 그러면 int 형으로 기본 1024 로 기록을 하였다 한다면,

유저가 이걸 1280 으로 바꾸었습니다. 하지만 Read 시에 해상도의 단락을 찾지 못하면,

여기에 있는 Default 로 1024로 마춰 줄 수도 있습니다.

짧은 내용이고 대부분 많은 분들이 아는 내용이지만. 혹여나 해서 올려봅니다^^


반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

함수포인터  (0) 2012.10.31
GetAsyncKeyState  (0) 2012.10.31
forceinline VS inline  (0) 2012.10.31
보다 더 완성도 있는 프로그램을 위한 assert(0) 함수  (0) 2012.10.31
enum with namespace  (0) 2012.10.31
반응형

inline : 컴파일시 알아서 인라인으로 함수를 만들지의 여부를 결정

forceinline : 무조건 inline 으로 생성

반응형

+ Recent posts