[ VC11-C++11 ] range base for - 1

C++0x 2012.08.21 19:20 Posted by 흥배

VC10에서 선보였던 C++11의 기능 중 강력하면서 사용하기 쉽고, 자주 사용한 기능이 아마 'auto'이지 않을까 생각합니다. 예전에 강연을 할 때 auto와 관련된 예제를 보여드리면 많은 분들이 아주 좋아하시더군요(좀 놀라기도 하시더군요^^). 어떤 분들은 딴 건 제쳐두고 이것 때문이라도 VC10을 사용해야겠다는 분들이 있었습니다.

 

이번 VC11에서도 'auto'와 같은 강력한 기능이 있습니다. 바로 'range base for' 입니다. 이것을 사용하면 반복문을 아주 쉽고, 강력하게 사용할 수 있습니다.

VC 특화 기능인 for each와 비슷하기 때문에 기존에 for each를 사용하고 있다면 이제는 range base for로 쉽게 바꾸어서 사용하면 됩니다.

 

 

예제를 통해 일반적인 for , VC for each, range base for문의 차이를 예제를 통해서 보겠습니다.

 

< 예제. 1 >

#include <iostream>

int main()

{

           int NumberList[5] = { 1, 2, 3, 4, 5 };

 

          

           std::cout << "일반적인 for " << std::endl;

          

           for( int i = 0; i < 5; ++i )

           {

                     std::cout << i << std::endl;

           }

 

 

           std::cout << "VC++ 특화의 for each" << std::endl;

 

           for each( int i in NumberList )

           {

                     std::cout << i << std::endl;

           }

 

 

           std::cout << "range base for " << std::endl;

 

           for( auto i : NumberList )

           {

                     std::cout << i << std::endl;

           }

 

           return 0;

}

 

< 실행 결과 >


<예제.1>을 보면 일반적인 for 문은

for( int i = 0; i < 5; ++i )

와 같이 시작과 종료 조건, 증가 값 이렇게 3개의 조건에 의해서 반복 됩니다.

 

그러나 range base for문은 VC만의 반복문인 for each와 비슷하게 데이터셋 변수와 이 데이터셋 요소의 타입을 선언하면 됩니다.

for( auto i : NumberList )

 

기존의 for 문에 비해서 또는 for each 보다도 간편해졌고, for each는 표준이 아닌 VC만의 기능인 것에 비해서 range base for C++ 표준 기능입니다.

 

range base for 문의 문법은 아래와 같습니다.

for ( for-range-declaration : expression ) statement

  

range base for 덕분에 반복문의 사용이 쉬워졌고, for 문을 사용할 때 종료 조건이 잘못되어 메모리 침범을 하는 위험도 피할 수 있게 되었습니다.

저작자 표시
신고

Visual C++ 10에서 디버깅하기

Visual C++ 10 2012.04.25 00:28 Posted by 흥배

예전에 Visual C++ 10과 관련된 책을 집필할 계획이 있어서 그때 작성한 글인데 책 출간 계획이 사라졌으므로(한참 전에)

그냥 공개합니다. 글은 초보자를 타겟으로 하고 있으며 Visula C++ 10의 디버깅 기능을 설명하고 있습니다.


도움이 되었으면 좋겠습니다^^



6장 디버깅.pdf


문서를 동의 없이 수정하지 않고, 출처만 남겨 주신다면 문서는 마음대로 배포하셔도 괜찮습니다^^


저작자 표시
신고

[미리 보는 C++11] 4. constexpr - 2

C++0x 2011.09.23 09:30 Posted by 흥배

constexpr를 클래스에 사용


constexpr을 클래스에서 사용하면 클래스를 정수로 사용할 수도 있으며 메타 템플릿 프로그래밍에서는 이전에는 복잡하게 처리하던 것을 아주 간단하게 처리할 수도 있습니다. C++ 메타 템플릿 프로그래밍에 관심이 많구나 자주 사용하고 있는 분들에게는 constexpr 덕분에 프로그래밍이 한결 편해지리라 생각합니다.

 

아래의 코드는 Integer 이라는 클래스를 constexpr을 사용하여 정수처럼 사용 합니다.

class Integer

{

private :

    int value ;

 

public :

    constexpr Integer() : value() { }

    constexpr Integer( int value ) : value(value) { }

 

    constexpr operator int() { return value ; }

} ;

 

int main()

{

    constexpr Integer size = 5 ; // 컴파일 타임에 정수로

 

    int x[size] ; // Integer::operator int()가 호출된다

 

    Integer object ; // 일반적인 클래스 인스턴스 화. 실행 시에 처리

    int y[object] ; // 당근 에러

}

출처 : http://cpplover.blogspot.com/2010/11/gccniconstexpr.html

 


또 메타 템플릿 프로그래밍에서는 아래와 같이 사용할 수도 있습니다.

#include <iostream>

 

struct pi {

    static constexpr double value = 3.14;

};

 

template <const double& r>

struct circle_area {

    static constexpr double value = r * r * pi::value;

};

 

struct radius {

    static constexpr double value = 2.5;

};

 

int main()

{

    constexpr double result = circle_area<radius::value>::value;

 

    static_assert(result == 19.625, "not equal");

    std::cout << result << std::endl;

}

출처 : http://d.hatena.ne.jp/faith_and_brave/searchdiary?word=constexpr&.submit=%B8%A1%BA%F7&type=detail

 

 

constexpr은 컴파일 할 때 결과가 이미 결정 나는 것은 컴파일 타임 때 처리를 해주어 실행 시에 불필요한 처리를 막아주고, 기존의 메타 템플릿 프로그래밍으로 까다롭게 만들었던 것을 아주 쉽게 구현할 수 있게 해줍니다.

 

C++11에서는 constexpr을 잘 사용하면 기존 보다 더 뛰어난 프로그래밍을 할 수 있으니 깊게 파고들 가치가 있다고 생각합니다.

저작자 표시
신고

[미리 보는 C++11] 3. constexpr - 1

C++0x 2011.09.15 09:00 Posted by 흥배

constexpr는 변수, 함수, 클래스를 컴파일 타임에 정수로 사용할 수 있도록 해줍니다. 즉 상수로 취급할 수 있는 작업은 컴파일 타임에 처리하도록 할 수 있습니다.

 

constexpr를 변수에 사용

constexpr int aa = 11;

이것은

const int aa = 11

와 같은 의미를 가집니다.

 

그러나 아래와 같이는 사용할 수 없습니다.

int input_num = 0;

constexpr int aa = input_num;  // 에러


constexpr
로 지정된 변수는 꼭 컴파일 시에 정수가 되기 때문에 변수 선언 시 대입이 정수 식이어야만 합니다. const와의 차이는 const는 컴파일 시에 정수가 아니어도 괜찮고 변수 선언 시 대입 값이 정수 식인 경우 정수 식이 되고, 그렇지 않은 경우는 단순히 const를 수식하는 것이 됩니다(이에 비해 constexpr는 꼭 정수 식이어야만 합니다).

 

 

 

constexpr를 함수에 사용

C++03에서는 아래의 코드는 에러가 됩니다.

int GetNum() { retun 5; }

int Numbers[ GetNum() ];

GetNum 함수는 상수 5를 반환 하는 것으로 이미 컴파일 시에 반환 값을 알 수 있습니다. 그러나 컴파일러는 GetNum 이라는 함수가 정수처럼 사용할 수 있는지 알 수 없으므로 정수로 취급하지 않습니다.

 

위 코드는 C++11constexpr를 사용하면 우리가 원하는 대로 GetNum 함수를 정수로 사용할 수 있습니다.

 

constexpr int GetNum() { retun 5; }

int Numbers[ GetNum() ];

 

constexpr를 함수에 사용할 때는 꼭 함수 본체는 { return expression; } 형태가 되어야만 합니다.

 

 

constexpr 변수는 비 constexpr 변수에 사용할 수 있으므로 아래와 같은 테크닉도 사용할 수 있다.

constexpr double power( double x, unsigned int y )

{

    return y == 1 ? x : x * power( x, y - 1 ) ;

}

 

int main()

{

    // 정수 식

    constexpr double a = power( 2, 32 ) ;

 

    // 정수 식이 아니다

    double x = 2 ; unsigned int y = 32 ;

    double b = power( x, y ) ;

}

(출처) http://cpplover.blogspot.com/2010/11/gccniconstexpr.html

 

 

그리고

const int base_HP = 200;

int NPC_Lv1_HP = base_HP + 0;

int NPC_Lv2_HP = base_HP + 200;

라는 코드는 정수 계산을 하는데 실행 시에 계산되는데 이것을 constexpr을 사용하여 컴파일 시에 계산되게 할 수 있습니다.

 

constexpr int AssignHP( int nPlusHP )

{

 return base_HP + nPlusHP;

}

 

int NPC_Lv1_HP = AssignHP( 0 );

int NPC_Lv2_HP = AssignHP( 200 );



 

저작자 표시
신고

[미리 보는 C++11] 2. override와 final

C++0x 2011.08.30 00:18 Posted by 흥배

현재의 표준 C++에서는 부모 클래스의 특정 멤버를 오버라이드 할 때 virtual을 앞에 붙입니다.

struct Base

{

  virtual void foo( int i );

};

 

struct Derived : Base

{

  virtual void foo( int i );

}

 위의 예제와 같은 작은 코드를 만질 때는 실수를 하지 않지만 실제 일을 할 때는 크고 많은 클래스를 다루다 보면 실수를 할 수 있습니다. 위 예제의 경우 아래와 같은 실수를 할 수 있습니다.

struct Derived : Base

{

  virtual void foo( float i );

}

위와 같이 실수를 하면 Derived의 foo 멤버함수는 Base foo 멤버함수를 오버라이드 하지 않게 됩니다. 이런 실수는 에러가 아니기 때문에 골치 아픈 삽질을 할 수도 있습니다.

이런 문제를 방지하기 위해서 override가 새로 생겼습니다.

struct Derived : Base

{

  virtual void foo( float i ) override;

}

이렇게 override를 사용하게 되면 컴파일 할 때 Base 클래스에

void foo( float i )가 없는데 오버라이드 한다고 에러를 발생시켜 줍니다.

 

 

때로는 Base 클래스의 특정 멤버함수를 Derived 클래스에서 오버라이드 하지 못하도록 막고 싶은 경우가 있을 것입니다. 이때는 final을 사용합니다.

struct Base

{

  virtual void foo( int i ) final;

};

 

struct Derived : Base

{

  virtual void foo( int i );

}

위의 코드에서는 Base 클래스의 foo 멤버함수를 final로 오버라이드 못하도록 해 놓았기 때문에 컴파일을 하면 에러가 발생합니다.

 

 

 

참고

위키피디아 http://en.wikipedia.org/wiki/C%2B%2B0x

 

저작자 표시
신고

C++0x가 드디어 C++11이 되었습니다.

C++0x 2011.08.15 16:02 Posted by 흥배

새로운 C++ 표준 작업이 언제쯤에나 끝날지 고대하고 있는 분들에게 반가운 소식이 있습니다.

드디어 C++0x 작업이 거의 마무리 되었습니다.

 

저번 주 수요일에 최종 국제 투표가 끝난 후 드디어 결과가 나왔는데 만장 일치로 승인이 되었습니다. 이제는 앞으로 몇 달 후에 ISO로부터 최종 발행만 기다리면 됩니다(즉 서류적인 절차만 남았습니다).

 

이로써 길게 길게 진행된 C++의 새로운 표준 작업은 끝나게 되어서 이제 C++11로 불리게 되었습니다( 이전에는 C++98, C++03이 있었습니다 ).

 

C++은 특정 회사가 주도하지 않고, 기존에 C++로 만들었던 코드가 문제 없이 동작해야 하고(표준 사양만 지켰다면), 성능과 편리성을 모두 가지려고 하니 많은 시간이 걸렸습니다.

 

아직 ISO에서 최종 문서가 나오지 않았고 무료로 최종 사양이 어떻게 되었는지 궁금한 분들은 http://t.co/5mjCzyJ 를 통해서 문서를 받아보기 바랍니다. 이 문서는 워킹 드래프트 3242 ISO에서 나올 사양 문서와 거의 같을 것입니다(참고로 ISO에서 나온 문서는 유료입니다).

 

이제 우리는 앞으로 나올 Visual C++이 과연 얼만큼 새로운 표준 기능을 구현해 줄지가 기대됩니다. 개인적으로 꽤 많은 부분이 구현되리라 생각하고 혹 빠진 부분은 예전의 tr1처럼 서비스 팩에서 구현되지 않을까 생각합니다.

 

C++ 새로운 표준이 정해졌으니 남보다 앞서기를 바라는 C++ 프로그래머들은 새로운 C++ 표준을 공부해 봅시다. ^^

 

근래에 바빠서 글을 거의 올리지 못했는데 다음 글은 새로운 C++ 표준의 바뀐 부분을 간단하게 설명하는 글을 올릴 예정입니다.

그리고 새로 추가되는 표준 라이브러리는 boost 라이브러리에서 많이 들어왔습니다. 그래서 지금이라도 boost 라이브러리를 다운로드 받으면 새로운 표준에 들어갈 라이브러리를 미리 사용할 수 있습니다.

 

 


참고

C++0x 사양

위키피디아

http://en.wikipedia.org/wiki/C%2B%2B0x

GCC feature 테이블

http://gcc.gnu.org/projects/cxx0x.html

 

boost 라이브러리

설치 버전 다운로드

http://www.boostpro.com/download/

공식 홈페이지

http://www.boost.org

 

 

 

저작자 표시
신고

C++ AMP

Visual C++ 10 2011.06.28 09:00 Posted by 흥배

C++ AMP라는 것을 들어보셨나요?근래에 나온 단어입니다.

AMP AcceleratedMassive Parallelism의 약자로 병렬 프로그래밍과 관련된 것입니다.

 

C++ AMP 2주 전의 AMD Fusion 컨퍼런스에서MicrosoftHerb Sutter씨가(MS의프로그램 언어 아키텍터 이자 C++ 표준 위원 멤버) 처음으로공개한 것으로 다음 버전의 Visual Studio(현재는Visual C++)에서 GPGPU 프로그래밍환경을 제공하는 것을 뜻합니다.

 

병렬 프로그래밍에서 대해서 조금 깊게 공부하신 분들은 아마 GPGPU라는것을 들어본 적이 있으리라 생각합니다. GPGPU는 간단하게 말하자면 GPU CPU 처럼 사용하자라는 것으로 GPU의 높은 성능을 사용하여 CPU와 똑 같게는 사용할 수는 없지만연산 처리에서 높은 병렬 기능을 사용하여 CPU보다 훨씬 뛰어난 결과를 얻을 수 있습니다.

 

현재까지 GPGPU 개발환경은NVIDIA Cuda와 오픈 아키텍처인 OpenCL,DirectX 베이스의 DirectCompute가 있습니다.

 

GPGPU 프로그래밍의 단점은 프로그래밍이 복잡하고 아직 레퍼런스가적다는 단점이 있어서 아직은 일반적인 프로그래밍 영역에 들어오지 못하고 있습니다(사실 아직 일반 병렬프로그래밍도 쉽게 사용하지 못하고 있으니..). 그래서 GPGPU가나온 것은 몇 년이 지났지만 아직 일부 전문 영역에서만 사용되고 있었습니다.

 

그러나 CPU 아키텍처가 멀티코어에서 헤테로지니어스 아키텍처(이기종의 CPU가 결합.CPU+GPU)로 서서히 넘어가고 있어서 자연스럽게GPGPU 프로그래밍이 부각되고 있었습니다. 하지만 아직도 개발환경의 뒷받침이 부족한 상태였는데드디어 우리 개발자에게 친숙한 Visual C++에서 이런 문제를 해결하려고 합니다.

 

C++ AMP는 쉽게 말하면Visual C++에서 GPGPU 프로그래밍을 지원하는 것을 말합니다. Visual C++의 뛰어난 개발환경을 토대로 하여 이때까지 복잡했던GPGPU 프로그래밍을 일반 프로그래밍 하듯이 사용할 수 있게 해줍니다. 이로써 GPGPU가 일반 프로그래밍 영역으로 들어 올 수 있는 큰 계기가 되었다고 생각합니다.

 

 

C++ AMP에 대해서 AMD Fusion 컨퍼런스에서 데모를 시연한 Daniel Moth의 블로그에올라온 글을 정리하면

개발자의 생산성과 이식성을 저해하지 않고 헤테로지니어스 하드웨어 프로그래밍의 허들을 낮게 하여 프로그래밍 일반영역에서 사용할 수 있도록 한다.

 

현재의 대 규모 병렬 하드웨어(CPU GPU)의 사용을 돕기 위한 것만이 아닌 코드의 투자를 미래에 대비한 디자인으로 하여 견고하도록 한다.

 

Visual Studio의 일부분으로 또 다른 컴파일러나 다른 구문을배울 필요가 없다.

 

현재의 C++ 언어를 사용하며 C나다른 파생 언어가 아니다.

 

Visual Studio vNext와 완벽하게 통합하여 지원한다. 편집, 빌드, 디버그, 프로파일러 등 Visual Studio의 다른 모든 기능이 C++ AMP와 같이 동작한다.

 

기존의 Concurrency Runtime의 일부로 STL와 비슷한 형태의 라이브러리를 제공하여 amp.h 헤더 파일을제공한다.

 

병렬화를 주 특징으로 하여 헤테로지니어스 하드웨어 위에서 거대한 다 차원 데이터를 아주 쉽게 동작한다.

 

유일의 코어 C++ 언어 확장을 도입한다.

 

DirectX(DirectCompute) 위에 구축하지만 C++ AMP에서는 DirectX의 모습은 나타나지 않는다( DirectX를 몰라도 상관 없다).

 

 

 

또 동 세미나에서 기조 연설을 한 Herb Sutter씨의 강연 중 C++ AMP에 관한 내용으로는

C++ AMP에 의해서 기존의C++에서 큰 변경을 가하지 않으면서 언어를 확장하는 점을 강조하여 새로운 언어가 만들어서 개발자에게 혼란을 주는 것을 피했다라는것을 알림.

 

언어 확장으로 restrict() 함수와 array_view라는 2개의 type Key로 잡음. restrict()는 프로세서 아키텍처에 따라서 실행가능한 기능에 제한을 거는 것이고 array_view는 불 균인한 메모리 공간으로의 접근으로 생기는문제를 회피하기 위한 것으로 메모리 공간을 N 차원의 배열로서 작업하는 것을 뜻한다. 메모리 공간의 추상화라고 할 수도 있다. restrict()array_view는 프로세서 아키텍쳐와 메모리 공간의 차이를 흡수할 수 있는 것으로 C++ AMP의 중요한 Key이다.

 

C++ AMP의 컴파일러는Visual Studio의 차기 버전에서 들어갈 예정으로 릴리스는 이번 연말로 예상하고 있다. 또이 컴파일러는 오픈 사양일 예정으로 Windows 상의 VisualStudio 뿐만이 아닌 그 이외의 개발 환경(C++ Builder이나 이클립스 등)에서도 이용할 수 있도록 AMD와 협력 하여 개발 중이라고 한다.

 

 

 

 

 

DirectXDirectCompute를 사용한다고 하니 C++ AMP를 사용한 프로그램은 Windows Vista 이상에서만 사용할 수 있을 것 같습니다(이유는 DirectCompute DirectX 10에서 지원하기 때문).

 

GPGPU에 관심은 있었지만 아직 시기상조라고 생각하는 분들은 C++ AMP가 나오면 개발 허들이 크게 내려가므로 본격적으로 준비를 해도 좋을 것 같습니다. AMD에서는 헤테로지니어스 컴퓨팅 프로그래밍의 전망을 CUDA 등의독자 사양에서 OpenCL이나 DirectCompute 등의오픈 사양으로 이동하고, 전문 프로그래머만 프로그래밍 하는 시대를2011년까지로 보고 그 이후로는 일반 프로그래머가 완전하게 C++로 프로그래밍하는 헤테로지니어스컴퓨팅 프로그램이 올 것으로 보고 있다고 합니다.

 

저도 이제 슬슬 GPGPU 프로그래밍 쪽으로 들어가볼 예정인데 일단조만간 OpenCL부터 시작해 볼까 합니다. 연말에 VS vNext가 나올 수도 있다고 하니 그때 꼭 C++ AMP를 공부해서 그 내용을 공유하도록 하겠습니다^^

 

 

 

참고

헤테로지니어스 멀티 코어 http://jacking.tistory.com/513

 

Daniel Moth씨의 블로그 http://www.danielmoth.com/Blog/

  위 글을 정리한 한블로그(일본어)
 http://blogs.msdn.com/b/hiroyuk/archive/2011/06/20/10176783.aspx

 

AMD Fusion 컨퍼런스에서의 데모

비디오 http://channel9.msdn.com/posts/Daniel-Moth-Blazing-fast-code-using-GPUs-and-more-with-C-AMP

슬라이드 http://ecn.channel9.msdn.com/content/DanielMoth_CppAMP_Intro.pdf

 

일본의 임프레스 사이트에 올라온 Herb Sutter씨의 기존 강연정리 글

http://pc.watch.impress.co.jp/docs/news/event/20110617_453939.html

 

저작자 표시
신고

STL의 컨테이너를 사용해보았다면 forward_list라고 해서 딱히 어려운 부분은 없습니다. 다만 forward_list이 단 방향 리스트라는 것과 다른 컨테이너에서는 지원하는 기능이 일부 없다는 것을 잘 숙지해야 합니다.

 

필요한 헤더 파일

forward_list는 이름과 같은 ‘forward_list’라는 헤더 파일을 포함해야 합니다.

#include <forward_list>

 

 

[예제] forward_list를 사용하여 요소 추가, 순회, 삭제하기

#include "stdafx.h"

#include <iostream>

#include <forward_list>

 

using namespace std;

 

 

int main()

{

           forward_list< int > flist;

 

 

           cout << "flist에 추가한 요소들 출력" << endl;

           // 추가하기

           auto iter = flist.before_begin();

           for( int i = 0; i < 5; ++i )

           {

                     iter = flist.insert_after( iter, i );

           }

                    

           // 순회

           for( iter = flist.begin(); iter != flist.end(); ++iter )

           {

                     cout << *iter << endl;

           }

 

           cout << endl;

           cout << "flist의 요소들 중 일부를 삭제한 후 남은 요소들 출력" << endl;

           // 순회 하면서 일부 요소 삭제

           auto prev_iter = flist.before_begin();

           iter = flist.begin();

           while( iter != flist.end() )

           {

                     if( 3 == *iter )

                     {

                                iter = flist.erase_after( prev_iter );

                                continue;

                     }

                     ++prev_iter;

                     ++iter;

           }

 

           // 순회

           for( iter = flist.begin(); iter != flist.end(); ++iter )

           {

                     cout << *iter << endl;

           }

 

           return 0;

}

 

< 결과 >


 

위 예제를 보면 아시겠지만 forward_list std::list에 비해 성능 면의 이점을 가지고 있지만 사용 측면에서는 조금 불편한 점이 좀 있습니다. 그러나 C와 비슷한 성능을 내고 싶은 경우에는 좋은 선택 기가 될 수도 있습니다.

 


참고

http://msdn.microsoft.com/ko-kr/library/ee373568.aspx

저작자 표시
신고

앞에까지는 STL의 알고리즘에 추가된 것들을 다루었는데 이번에는 컨테이너 하나를 소개하겠습니다. 사실 이 컨테이너는 저도 얼마 전까지만 하더라도 새로 추가 된지 몰랐습니다.^^;

 

새로 추가된 컨테이너의 이름은 forward_list입니다.

이름을 들어보니 대충 어떤 컨테이너인지 감이 오시죠?^^ 네 이 컨테이너는 기존의 list 컨테이너와 비슷한 종류의 컨테이너입니다.

 

 

forward_list를 만든 이유

표준 라이브러리(STL)에는 이미 리스트(std::list) 라이브러리가 있습니다. 이것은 쌍 방향 리스트입니다. list는 사용하기는 편하지만 사용 메모리나 처리 속도에 조금 아쉬운 점이 있습니다. 또 대 부분의 상황에서 쌍 방향 리스트가 필요한 경우보다는 단 방향 리스트만으로 충분한 경우가 자주 있습니다. 이런 이유로 C++0x에서는 단 방향 리스트를 추가하기로 했습니다.

 

 

forward_list의 설계 방침

1. 특별한 이유가 없다면 forward_list는 기존의 list의 설계에 맞춘다.

2. 설계 상의 선택 기가 여러 개인 경우 성능(속도와 사이즈)을 최우선 한다(C의 구조체로 구현하는 경우와 비교하여 Zero Overhead로 한다).

3. std::list insert eraseforward_list에서도 제공할 수 있지만 구현이 복잡해지고 성능 측면에서 좋지 않으므로 제공하지 않는다.

4. 다른 STL의 컨테이너들에 있는 size 함수를 제공하지 않는다. 이유는 요소 수를 보존하는 멤버를가지고 있으면 C언어에서 구현한 것과 비교해서 불필요한 메모리를 사용한다. 만약 이런 멤버를 가지고 있지 않으면서 size 함수를 지원하면 호출할 때마다 모든 요소를 세어야 하므로 계산량이 O(N)이 된다(그런데 유저는 다른 컨테이너와 같이 size의 계산량이 작을 것이라고 생각할 수 있다). 또 이미 unordered와 같은 연상 컨테이너도 기존의 요소를 만족하지 않고 있다.

 

 

STL list 컨테이너와 다른 점

forward_list는 기존의 list와 아래와 같은 점이 다릅니다.

1. forward_list는 단 방향 리스트(singly-linked-list)이다. 각 요소는 그 다음 요소를 가리키는 포인터를 하나만 가지고 있다(list은 양 방향 리스트).

2. (단 방향 리스트이므로) list에 비해서 메모리를 작게 사용한다. 이것은 각 요소의 메모리만이 아닌 컨테이너 그 자체의 사이즈도 작다. int 형에 대해서 list 12바이트라면 forward_list 8바이트이다(64비트에서는 각각 24, 16).

3. list에 비해 삽입/삭제 속도가 더 빠르지만 그 차이는 크지는 않다

4. 한 방향으로만 이동할 수 있다.

5. 삽입과 삭제는 지정한 요소의 다음 요소만 가능하다.

 

 

forward_list의 멤버 리스트

기능

멤버

대입

assign

반복자

befor_begin

 

cbefore_begin

 

begin

 

end

 

cbegin

 

cend

비었는지 조사

empty

현재 크기(size)

지원 안함

사이즈 변경

resize

모두 삭제

clear

선두에 추가

push_front

선두 요소 삭제

pop_front

선두 요소 참조

front

삽입

insert_after

삭제

erase_after

조건 삭제

remove

 

remove_if

중복 요소 삭제

unique

교환

swap

병합

merge

정렬

sort

반전

reverse

 

저작자 표시
신고

C++03까지의 STL에는 데이터셋에서 가장 작은 요소를 찾을 때는 min_element, 가장 큰 요소를 찾을 때는 max_element를 사용하였습니다.

그런데 만약 최소와 최대를 동시에 찾을 때는 어쩔 수 없이 min_element max_element를 각각 호출해야 하는 불필요한 불편한 점이 있었습니다.

 

C++0x에서는 이런 불편함을 개선하기 위해 한번에 최소와 최고를 찾아주는 minmax_element 알고리즘이 새로 생겼습니다.

 

 

minmax_element

template<class ForwardIterator>

    pair< ForwardIterator, ForwardIterator >

        minmax_element( ForwardIterator _First, ForwardIterator _Last );

template<class ForwardIterator, class BinaryPredicate>

    pair< ForwardIterator, ForwardIterator >

        minmax_element( ForwardIterator _First, ForwardIterator _Last, BinaryPredicate _Comp );

 

minmax_element 알고리즘에는 조건자를 사용하는 버전과 조건자를 사용하지 않은 버전 두 가지가 있습니다. 데이터셋의 자료형이 유저 정의형(class struct를 사용한)이라면 조건자가 있는 버전을 사용합니다.

 

< 예제 코드 >

#include <iostream>

#include <algorithm>

using namespace std;

 

 

int main()

{

           int Numbers[10] = { 50, 25, 20, 7, 15, 7, 10, 2, 1, 3 };

          

           pair<int*, int*> MinMaxValue = minmax_element( &Numbers[0], &Numbers[10] );

 

           cout << "최소 값 : " << *MinMaxValue.first << endl;

           cout << "최대 값 : " << *MinMaxValue.second << endl;

          

           return 0;

}

 

< 결과 >


 

저작자 표시
신고

is_heap is_heap_until는 앞서 소개했던 is_sorted, is_sorted_until과 비슷한 알고리즘입니다. 차이가 있다면 is_heap is_heap_until는 정렬이 아닌 Heap을 다룬다는 것만 다릅니다.

 

is_heap은 데이터셋이 Heap으로 되어 있는지 아닌지, is_heap_until는 데이터셋에서 Heap이 아닌 요소의 첫 번째 위치를 반환합니다.

 

is_heap

template<class RandomAccessIterator>

    bool is_heap(

        RandomAccessIterator _First,

        RandomAccessIterator _Last

    );

template<class RandomAccessIterator, class BinaryPredicate>

    bool is_heap(

        RandomAccessIterator _First,

        RandomAccessIterator _Last,

        BinaryPredicate _Comp

    ); 

 

 

is_heap_until

template<class RandomAccessIterator>

    bool is_heap_until(

        RandomAccessIterator _First,

        RandomAccessIterator _Last

);

template<class RandomAccessIterator, class BinaryPredicate>

    bool is_heap_until(

        RandomAccessIterator _First,

        RandomAccessIterator _Last,

        BinaryPredicate _Comp

);

 

 

is_heap is_heap_until는 각각 조건자를 사용하는 버전과 사용하지 않는 버전 두 개가 있습니다. 조건자를 사용하지 않는 경우는 operator< 를 사용합니다.

 

 

그럼 is_heap is_heap_until을 사용한 아주 간단한 예제 코드를 봐 주세요^^

#include <iostream>

#include <algorithm>

using namespace std;

 

 

int main()

{

           int Numbers1[10] = { 50, 25, 20, 7, 15, 7, 10, 2, 1, 3 };

           int Numbers2[10] = { 50, 25, 20, 7, 15, 7, 10, 6, 11, 3 };

           int Numbers3[10] = { 50, 25, 20, 7, 15, 16, 12, 3, 6, 11 };

          

          

           bool IsResult = false;

           IsResult = is_heap( &Numbers1[0], &Numbers1[10], [](int x, int y) { return x < y; } );

           cout << "Numbers1 Heap인가 ? " << IsResult << endl;

 

           IsResult = is_heap( &Numbers2[0], &Numbers2[10], [](int x, int y) { return x < y; } );

           cout << "Numbers2 Heap인가 ? " << IsResult << endl;

 

           IsResult = is_heap( &Numbers3[0], &Numbers3[10] );

           cout << "Numbers3 Heap인가 ? " << IsResult << endl;

 

           cout << endl;

           int* NumIter = is_heap_until( &Numbers2[0], &Numbers2[10], [](int x, int y) { return x < y; } );

           cout << "Numbers2에서 Heap되지 않은 첫 번째 위치의 값 : " << *NumIter << endl;

 

           return 0;

}

 

< 결과 >

 

 

 

ps : 자료구조 Heap에 대해서 잘 모르시는 분들은 아래의 글을 참고해 주세요

http://blog.naver.com/ctpoyou/105423523


저작자 표시
신고

is_sorted는 데이터셋이(컨테이너나 배열) 정렬되어 있다면 true를 반환하고, 그렇지 않다면 false를 반환 합니다.

is_sorted_until는 데이터셋에서 정렬되어 있지 않는 요소의 첫 번째 위치를 반환합니다.

 

is_sortedis_sorted_until의 원형은 아래와 같습니다.

is_sorted

template<class ForwardIterator>

    bool is_sorted( ForwardIterator _First, ForwardIterator _Last );


template<class ForwardIterator, class BinaryPredicate>

    bool is_sorted( ForwardIterator _First, ForwardIterator _Last, BinaryPredicate _Comp );

 

 

is_sorted_until

template<class ForwardIterator>

    ForwardIterator is_sorted_until( ForwardIterator _First, ForwardIterator _Last);

 

template<class ForwardIterator, class BinaryPredicate>

    ForwardIterator is_sorted_until( ForwardIterator _First, ForwardIterator _Last,

               BinaryPredicate _Comp );

 

위의 is_sortedis_sorted_until의 원형을 보시면 알겠지만 조건자(함수객체)를 사용하는 버전과 사용하지 않는 버전 두 가지가 있습니다.

조건자를 사용하지 않는 경우 기본으로 operator<가 적용됩니다.

 

프로그래머는 코드로 이해하죠? ^^ 그럼 바로 예제 코드 들어갑니다.

이번 예제는 간단하게 만들기 위해 정수 배열을 사용해 보았습니다. 아마 STL을 이제 막 공부하고 있는 분들은 알고리즘을 STL의 컨테이너에만 사용할 수 있는 것으로 알고 있는 분들도 있을텐데 그렇지 않습니다. 아래 예제는 int 형 배열을 사용하였습니다.

 

< 예제 코드 >

#include <iostream>

#include <algorithm>

using namespace std;

 

 

int main()

{

           int Numbers1[5] = { 1, 2, 3, 4, 5 };

           int Numbers2[5] = { 5, 4, 3, 2, 1 };

           int Numbers3[5] = { 1, 2, 4, 3, 5 };

           bool IsResult = false;

 

          

           IsResult = is_sorted( &Numbers1[0], &Numbers1[5], [](int x, int y) { return x < y; } );

           cout << "Numbers1. 오름 차순 ? " << IsResult << endl;

 

           IsResult = is_sorted( &Numbers2[0], &Numbers2[5], [](int x, int y) { return x > y; } );

           cout << "Numbers2. 내림 차순 ? " << IsResult << endl;

 

           IsResult = is_sorted( &Numbers3[0], &Numbers3[5], [](int x, int y) { return x < y; } );

           cout << "Numbers3. 오름 차순 ? " << IsResult << endl;

 

           cout << endl;

           cout << "is_sorted에서 조건자(함수객체)를 생략한 경우 " << IsResult << endl;

           IsResult = is_sorted( &Numbers1[0], &Numbers1[5] );

           cout << "Numbers1 is_sorted의 결과는 ? " << IsResult << endl;

           IsResult = is_sorted( &Numbers2[0], &Numbers2[5] );

           cout << "Numbers2 is_sorted의 결과는 ? " << IsResult << endl;

 

           cout << endl;

           int Numbers4[8] = { 1, 2, 3, 5, 4, 5, 7, 8 };

           int* NumIter = is_sorted_until( &Numbers4[0], &Numbers4[5], [](int x, int y) { return x < y; } );

           cout << "Numbers4에서 정렬되지 않은 첫 번째 위치의 값 : " << *NumIter << endl;

 

           return 0;

}

 

< 결과 >


 

 

저작자 표시
신고

Native C++ 개발자를 위한 C++/CLI

C++/CLI 2011.04.20 09:00 Posted by 흥배

제가 http://vsts2010.tistory.com/category/C++/CLI 에 올린 글을 정리한 것으로 보기 편하도록 정리하여 pdf 파일로 만들었습니다.

이후 관련 글을 포스팅 하면 일정 시간이 지난 후 다시 이 글에도 추가하여 배포할 예정입니다.





저작자 표시
신고

이번에 설명할 is_partitioned, partition_point는 그 이름처럼 앞서 소개한 partition_copy와 관계가 있는 알고리즘 입니다.

 

is_partitioned의 원형

template<class InputIterator, class BinaryPredicate>

    bool is_partitioned(

        InputIterator _First,

        InputIterator _Last,

        BinaryPredicate _Comp

    );

 

partition_point의 원형

template<class ForwardIterator, class Predicate>

    ForwardIterator partition_point(

        ForwardIterator _First,

        ForwardIterator _Last,

        Predicate _Comp

    );

 

is_partitioned는 데이터셋의 요소가 전반 부와 후반 부 두 개로 나누어져 있는지 조사할 때 사용하고, partition_point는 두 개로 나누어져 있는 데이터셋에서 후반 부의 첫 번째 요소를 가리키는 반복자를 반환합니다.

 

약간 설명이 애매하죠?^^;

예를 들어 설명하면 온라인 FPS 게임에서 8명이 각각 4명씩 레드 팀과 블루 팀으로 나누어서 게임을 하는 경우 vector로 된 StagePlayers(온라인 게임에서 방에 들어온 유저들을 저장)에 앞 부분에는 레드 팀 플레이어 4명을 차례로 저장하고, 그 이후에 블루 팀 플레이어를 저장하고 있는지 조사하고 싶을 때 is_partitioned 알고리즘을 사용하면 알 수 있습니다(맞다면 true를 반환합니다). 그리고 StagePlayers에서 블루 팀의 첫 번째 플레이어에 접근하고 싶다면 partition_point를 사용합니다.  


나름 쉽게 설명한다고 했는데 이해 가시나요? 만약 이해가 안 간다면 예제 코드를 봐 주세요^^

 

< 예제 >

#include <iostream>

#include <algorithm>

#include <vector>

#include <list>

using namespace std;

 

struct PLAYER

{

           int CharCD;

           bool IsRedTeam;

};

 

 

int main()

{

           vector< PLAYER > StagePlayers1;

           PLAYER player1; player1.CharCD = 1;         player1.IsRedTeam = true;           StagePlayers1.push_back( player1 );

           PLAYER player2; player2.CharCD = 2;         player2.IsRedTeam = true;           StagePlayers1.push_back( player2 );

           PLAYER player3; player3.CharCD = 3;         player3.IsRedTeam = true;           StagePlayers1.push_back( player3 );

           PLAYER player4; player4.CharCD = 4;         player4.IsRedTeam = false;                      StagePlayers1.push_back( player4 );

           PLAYER player5; player5.CharCD = 5;         player5.IsRedTeam = false;                      StagePlayers1.push_back( player5 );

           PLAYER player6; player6.CharCD = 6;         player6.IsRedTeam = false;                      StagePlayers1.push_back( player6 );

           PLAYER player7; player7.CharCD = 7;         player7.IsRedTeam = false;                      StagePlayers1.push_back( player7 );

 

           bool IsPartitioned = is_partitioned( StagePlayers1.begin(), StagePlayers1.end(),

                                                                                     []( PLAYER player ) -> bool { return player.IsRedTeam; } );

           if( IsPartitioned ) {

                     cout << "레드 팀과 블루 팀으로 구분 되어 나누어져 있습니다." << endl;

           } else {

                     cout << "레드 팀과 블루 팀으로 구분 되어 있지 않습니다." << endl;

           }

 

           vector< PLAYER >::iterator IterFirstBlueTeamPlayer = partition_point( StagePlayers1.begin(),StagePlayers1.end(),

                                                     []( PLAYER player ) -> bool { return player.IsRedTeam; } );

           if( IterFirstBlueTeamPlayer != StagePlayers1.end() ) {

                     cout << "첫 번째 블루 팀 플레이어. 캐릭터 코드 : " << (*IterFirstBlueTeamPlayer).CharCD << endl;

           }

 

                    

           vector< PLAYER > StagePlayers2;

           StagePlayers2.push_back( player7 );

           StagePlayers2.push_back( player6 );

           StagePlayers2.push_back( player1 );

           StagePlayers2.push_back( player5 );

           StagePlayers2.push_back( player4 );

           StagePlayers2.push_back( player3 );

           StagePlayers2.push_back( player2 );

          

           IsPartitioned = is_partitioned( StagePlayers2.begin(), StagePlayers2.end(),

                                                                                     []( PLAYER player ) -> bool { return player.IsRedTeam; } );

           if( IsPartitioned ) {

                     cout << "레드 팀과 블루 팀으로 구분 되어 나누어져 있습니다." << endl;

           } else {

                     cout << "레드 팀과 블루 팀으로 구분 되어 있지 않습니다." << endl;

           }

 

           getchar();

           return 0;

}

 

< 결과 >

 

예제 코드를 보니 쉽게 이해 되시죠? 그럼 저는 아는 걸로 생각하고 다음 포스팀에서는 다른 알고리즘을 설명하겠습니다^^

 

 

저작자 표시
신고

partition_copy 알고리즘은 하나의 집단에서 서로 다른 두 개의 집단으로 나눌 때 사용하는 것이라고 아주 간단하게 말할 수 있습니다.

 

partition_copy

template<class InputIterator, class OutputIterator1, class OutputIterator2, class Predicate>

    pair<OutputIterator1, OutputIterator2>

        partition_copy(

            InputIterator _First,

            InputIterator _Last,

            OutputIterator1 _Dest1,

            OutputIterator2 _Dest2,

            Predicate _Pred

        );

 데이터셋의 _First _Last 사이에 있는 각 요소 x를 조건자 _Pred에 인자를 넘겼을 때 true를 반환하면 x _Dest1, false를 반환하면 _Dest2에 복사하고 지정된 구간의 모든 요소를 다 처리하면 OutputIterator 값을 pair로 반환한다

 

그럼 좀 더 쉽게 이 알고리즘을 어떤 경우에 사용하는지 알 수 있도록 간단한 예제를 하나 보여드리겠습니다.

 ) 게임 아이템들을 팔 수 있는 것과 팔 수 없는 것으로 나누어라

#include <iostream>
#include <algorithm>
#include <vector>
#include <list>
using namespace std;

struct ITEM
{
    int nItemCode;
    bool bEnableSell;
};

int main()
{
    vector< ITEM > AllItems;
    ITEM item1; item1.nItemCode = 1;    item1.bEnableSell = false;        AllItems.push_back( item1 );
    ITEM item2; item2.nItemCode = 2;    item2.bEnableSell = true;        AllItems.push_back( item2 );
    ITEM item3; item3.nItemCode = 3;    item3.bEnableSell = true;        AllItems.push_back( item3 );
    ITEM item4; item4.nItemCode = 4;    item4.bEnableSell = false;        AllItems.push_back( item4 );
    ITEM item5; item5.nItemCode = 5;    item5.bEnableSell = true;        AllItems.push_back( item5 );
    ITEM item6; item6.nItemCode = 6;    item6.bEnableSell = false;        AllItems.push_back( item6 );
    ITEM item7; item7.nItemCode = 7;    item7.bEnableSell = true;        AllItems.push_back( item7 );

    ITEM UnItem; UnItem.nItemCode = 0;
    list< ITEM > SellItems( 7, UnItem );            
    list< ITEM > UnSellItems( 7, UnItem );
    
    pair<list< ITEM >::iterator, list< ITEM >::iterator > SeperateItems;
    SeperateItems = partition_copy( AllItems.begin(), AllItems.end(),
                                                 SellItems.begin(),
                                                 UnSellItems.begin(),
                                                   []( ITEM& item ) { return item.bEnableSell; } );

    cout << "팔 수 있는 아이템" << endl;
    for each( ITEM item in SellItems )
    {
        if( item.nItemCode <= 0 ) {
            continue;
        }
        cout << "아이템 코드 : " << item.nItemCode << endl;
    }

    cout << endl << endl;

    cout << "팔 수 없는 아이템" << endl;
    for( auto Iter = UnSellItems.begin(); Iter != UnSellItems.end(); ++Iter )
    {
        if( Iter->nItemCode <= 0 ) {
            continue;
        }
        cout << "아이템 코드 : " << Iter->nItemCode << endl;
    }
    
    getchar();
    return 0;
}

< 결과 >


partition_copy를 사용할 때 한 가지 주의할 점은 결과를 다른 컨테이너에 복사를 하므로 해당 컨테이너에 공간이

확보되어 있어 있어야 합니다. 그래서 위의 예제에도

ITEM UnItem; UnItem.nItemCode = 0;
list< ITEM > SellItems( 7, UnItem );            
list< ITEM > UnSellItems( 7, UnItem );

로 더미 값을 넣어서 복사할 공간을 확보하고 있습니다.

 

참조 : http://msdn.microsoft.com/ko-kr/library/ee384416.aspx

 

저작자 표시
신고

이번이 SafeInt 라이브러리에 대한 4번째 글이면서 마지막 글입니다. 이전 회의 제 글을 보셨다면 SafeInt가 어떤 것인지, 어떻게 사용하는지 대부분 알게 되셨습니다. 이번에는 SafeInt의 함수 버전에 대해서 설명합니다.

 

 

SafeInt 함수

SafeInt 라이브러리에는 SafeInt 클래스의 인스턴스를 만들지 않고 사용할 수 있도록 몇 개의 함수를 지원하고 있습니다. SafeInt 함수는 정수 오버플로우가 발생하지 않도록 단일 수치 연산을 보호하고 싶을 때 사용합니다. 그리고 복수의 수치 연산을 보호하고 싶을 때는 SafeInt 클래스를 사용하고 함수 버전들을 반복하여 사용하는 것보다는 SafeInt 클래스를 사용하는 것이 더 효율적이라고 합니다.

 

함수

설명

SafeAdd

두 개의 값을 더한다

SafeCast

다른 형으로 캐스팅한다

SafeDivide

두 개의 값으로 나눈다

SafeEquals, SafeGreaterThan, SafeGreaterThanEquals, SafeLessThan, SafeLessThanEquals, SafeNotEquals

2개의 값을 비교한다. 이 함수들을 사용하면 서로 형이 다른 두 개의 값을 형 변환하지 않고 비교할 수 있다

SafeModulus

나머지를 구한다

SafeMultiply

두 개의 값을 곱한다

SafeSubtract

두 개의 값을 뺀다.

 

 

함수들의 이름만 봐도 어떤 것인지 알 수 있고, 이미 어떤 역할을 하는지 아실 테니 따로 길게 설명은 하지 않겠습니다. 아주 간단한 예제를 보여드릴 테니 그것을 보고 대충 어떻게 사용하는지 이해하고 부족한 부분은 MSDN을 참고해 주세요

 

#include <iostream>

#include <safeint.h>

using namespace msl::utilities;

 

 

int main()

{

           unsigned char X1 = 123;

           unsigned char X2 = 200;

           unsigned char X3 = 0;

           if( false == SafeAdd( X1, X2, X3 ) ) {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

          

           unsigned int AA = 100;

           short BB = 101;

           if( false == SafeEquals( AA, BB ) ) {

                     std::cout << "AA BB는 서로 다릅니다" << std::endl;

           }

 

           getchar();

           return 0;

}

< 결과 >


 

 


참고

SafeInt 함수

http://msdn.microsoft.com/ko-kr/library/dd575188.aspx

 

저작자 표시
신고

SafeInt의 예외처리 두 번째 방법은 기본 예외처리 정책을 컴파일 타임에서 선언하는 것입니다.

앞 글에서 우리가 만든 예외처리 클래스를 사용하기 위해 SafeInt를 정의할 때 예외처리 클래스를 템플릿 파라미터의 인자로 넘겼는데 이번에 소개하는 방법은 이 예외처리 클래스 템플릿 인자를 미리 정의해 놓는 것입니다.

 

방법은 SafeInt 헤더파일을 선언하기 전에 #define 문으로 _SAFEINT_DEFAULT_ERROR_POLICY에 예외처리에 사용할 클래스를 선언합니다.

 

// MySafeIntException 이 우리가 정의한 예외처리 클래스입니다.

#define _SAFEINT_DEFAULT_ERROR_POLICY MySafeIntException

 

#include <safeint.h>

 

이렇게 #define 문으로 SafeInt에서 사용할 예외처리 클래스를 선언하고 예외처리 클래스를 정의하면 SafeInt를 선언할 때 템플릿 파라미터로 형만 선언하면 이후 SafeInt를 사용하다가 예외가 발생하면 #define에서 선언한 예외처리 클래스를 호출합니다.

 

#include <iostream>

#define _SAFEINT_DEFAULT_ERROR_POLICY MySafeIntException

 

#include <safeint.h>

using namespace msl::utilities;

 

 

class MySafeIntException : public SafeIntException

{

public:

           static void SafeIntOnOverflow()

           {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

 

           static void SafeIntOnDivZero()

           {

                     std::cout << "0으로 나누기 발생!!" << std::endl;

           }

};

 

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

           SafeInt<unsigned int> X2(1234567);

          

           SafeInt<unsigned int> X3 = X1 * X2;

          

           getchar();

           return 0;

}

 

이전 회에서 소개한 방법과 별 차이 없이 #define 문을 사용한다는 것만 다르니 쉽게 이해했으리라 생각합니다.

 

이제 SafeInt를 사용할 때는 예외처리를 위해 우리는 3가지 방식을 사용할 수 있습니다,

1. try{} catch{}

2. SafeInt에서 사용할 예외처리 클래스를 정의 후 SafeInt 선언 시에 사용

3. #define 문을 사용하여 SafeInt에서 사용할 기본 예외처리 클래스 선언

 

SafeInt 라이브러리는 클래스만 있는 것이 아닙니다. 함수로도 지원합니다. SafeInt의 함수 버전은 SafeInt에 대한 마지막 글이 될 다음 포스팅을 통해서 설명하겠습니다.

 

저작자 표시
신고

이전 글에서 SafeInt를 사용하여 정수 연상을 할 때 오버플로우가 발생할 때 예외처리를 하지 않으면 릴리즈 모드에서는 크래쉬가 발생한다고 하였습니다. 그래서 SafeInt를 사용할 때 꼭 예외처리를 사용해야 합니다.

 

< 코드 1. SafeInt 연산 시 예외처리 구현 >

#include <iostream>

#include <safeint.h>

 using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

           SafeInt<unsigned int> X2(1234567);

          

           try

           {

                     SafeInt<unsigned int> X3 = X1 * X2;

           }

           catch(SafeIntException e)

           {

                     std::cout << "overflow!!. ErrorCode : " << e.m_code << std::endl;

           }

 

           getchar();

           return 0;

}

 

< 결과 >

 

 <코드 1>의 결과를 보면 예외 처리에 의해서 프로그램이 크래쉬 되지 않음을 알 수 있습니다. 그런데 SafeInt를 사용할 때마다 <코드1> 처럼 매번 예외처리를 구현한다는 것은 너무 불편합니다. 불편하면 SafeInt를 기피하게 되겠죠. -_-

다행히 SafeInt는 이런 것도 다 감안해서 만들어져 있습니다. 사전에 예외처리를 미리 정의 해 놓으면 매번 예외처리를 정의할 필요가 없습니다.

SafeInt의 예외처리 정의는 두 가지 방법이 있습니다. 이번 회는 두 가지 방법 중 첫 번째 방법을 소개하고 두 번째 것은 다음 회에 소개 하겠습니다.

 

 

 

SafeInt의 예외처리 방법 1

SafeInt의 기본 예외 처리 클래스를 상속 받아서 우리가 원하는 방식으로 정의한 후 그것을 SafeInt의 생성자에 인자로 넘겨주면 SafeInt로 연산 작업을 할 때 예외가 발생하면 우리가 정의한 예외처리를 호출합니다. 백문이불여일견이라고 바로 다음의 코드를 봐 주세요. 아주 간단합니다.

 

< 코드 2. SafeInt의 예외처리 방법 1 >

#include <iostream>
#include <safeint.h>

using namespace msl::utilities;

 

class MySafeIntException : public SafeIntException

{

public:

           static void SafeIntOnOverflow()

           {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

 

           static void SafeIntOnDivZero()

           {

                     std::cout << "0으로 나누기 발생!!" << std::endl;

           }

};

 

int main()

{

           SafeInt<unsigned int, MySafeIntException> X1(1234567);

           SafeInt<unsigned int, MySafeIntException> X2(1234567);

          

           SafeInt<unsigned int, MySafeIntException> X3 = X1 * X2;

          

           getchar();

           return 0;

}

 

< 결과 >

 

 <코드 2>의 결과를 보면 SafeInt로 연산 작업을 할 때 예외처리를 같이 정의하지 않아도 오버플로우로가 발생하면 우리가 정의한 클래스의 멤버 함수를 호출 합니다.

 

SafeInt의 생성자에 인자로 넘기는 예외처리 클래스는 꼭 SafeIntException 클래스를 상속 받고 static void SafeIntOnOverflow()static void SafeIntOnDivZero()를 재정의해야 합니다.

 

이것으로 <코드 1>의 예외처리 방식보다는 좀 편리해졌습니다. 그런데 아마 지금도 마음에 들지 않는 분이 있을 것 같습니다. SafeInt를 생성 할 때마다 매번 예외처리 클래스를 인자로 넘기는 것도 귀찮은 분이 있을 것 같네요. 이런 분들을 위해서 SafeInt는 또 하나더 예외처리 방법을 지원합니다. 그것은 다음 회에서 설명하겠습니다.^^
저작자 표시
신고
#include <iostream> 

int main()

{

           unsigned int X1 = 1234567;

           unsigned int X2 = 1234567;

           unsigned int X3 = X1 * X2;

           std::cout << "X3 = " << X3 << std::endl;

 

           unsigned __int64 BigX1 = 1234567;

           unsigned __int64 BigX2 = 1234567;

           unsigned __int64 BigX3 = BigX1 * BigX2;

           std::cout << "BigX3 = " << BigX3 << std::endl;

 

           getchar();

           return 0;

}

 

<코드1>를 실행하면 변수 X3 BigX3의 값이 서로 같을까요? 혹시 같다고 생각하시는 분들은 unsigned int의 최대 값이 얼마인지 MSDN에서 검색해 보세요... 네 결과는 서로 다릅니다. 둘 다 계산에 사용하는 값은 같지만 결과가 다르게 나오는 이유는 unsigned int로는 X1 X2를 곱해서 나온 값을 보관할 수 없기 때문입니다. X3 = X1 * X2에서 X3는 오버플로우가 발생하여 상위 비트가 삭제되어 올바른 계산 값이 저장되지 않습니다.

 

<코드 1의 결과>


 

<코드 1>의 코드는 사실 별로 길지 않은 코드이기 때문에 실행하기 전에 오버플로우가 발생하리라는 것을 충분히 예상할 수 있고, 혹은 실행 후에 X3의 값이 예상하지 못한 값이 들어가 있어도 문제를 쉽게 파악할 수 있을 것입니다. 그러나 우리가 만드는 애플리케이션은 복잡하고 긴 코드를 가지고 있습니다. <코드 1>과 같은 오버플로우에 의해서 버그가 발생하면 쉽게 버그를 찾기 힘들고 특히 오버플로우에 의해 애플리케이션이 오 동작하여 크래쉬가 발생할 수도 있습니다.

 

안전한 애플리케이션을 만들기 위해서는 안전한 코드를 만들어야 합니다. 보통 안전한 코드를 생각하면 포인터 조작과 문자열 조작을 주로 중요하게 다루고 VC++에서도 안전한 문자열 조작 위해 ‘_s’가 붙은 문자열 조작함수를 사용하도록 VC++에서 종용하고 있어서 요즘은 대 부분 이것을 사용하고 있습니다. 그러나 정수 계산에 대해서는 안전한 코드를 위해 지원해 주는 것이 없었습니다.

 


SafeInt 란?

VC++ 10에서는 안전한 정수 계산을 위해서 새로운 라이브러리를 지원해 줍니다. 이 라이브러리의 이름은 SafeInt 입니다. SafeIntC++의 템플릿으로 만들어서 char 형에서 __int64 형까지 8비트에서 64비트 사이의 크기를 가진 모든 정수 형을 사용할 수 있습니다.

SafeInt 라이브러리를 사용하면 결과를 담을 변수의 형 보다 큰 정수 값 연산을 하거나 0으로 나누기 연산을 할 때 발생하는 오버플로우를 감지 할 수 있습니다.


 

SafeInt 사용

SafeInt를 사용하기 위해서는 헤더 파일 safeint.h 를 포함하고 msl::utilities 이름 공간을 선언해야 합니다.


 < 코드 2 >

#include <iostream>

#include <safeint.h>

using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

          

           unsigned int x2 = 1234567;

           SafeInt<unsigned int> X2(x2);


           SafeInt<unsigned int> X3 = X1 * X2;     

        

           getchar();

           return 0;

}

 

<코드 2>를 디버그 모드에서 실행하면 아래와 같은 ASSERT 메시지가 발생합니다.


 

이유는 오버플로우가 발생했기 때문입니다. 그러나 릴리즈 모드에서는 ASSERT 메시지가 발생하지 않습니다. 다만 크래쉬가 발생합니다. -_-;;

오버플로우에 의해서 디버그 모드에서는 ASSERT 메시지, 릴리즈 모드에서는 크래쉬가 발생하는 이유는

SafeInt<unsigned int> X3 = X1 * X2;

에서 예외가 발생하기 됩니다. SafeInt를 사용하는 경우 오버플로우가 발생하면 예외를 발생시키기 때문에 try{} catch{}로 예외를 처리해 주지 않으면 안됩니다. 예외 처리가 올바르게 하면 오버플로우가 발생했을 때 발생하는 문제를 올바르게 대처하던가 어디에서 어떻게 오버플로우가 발생했는지 쉽게 알 수 있습니다.

 

그리고 SafeInt는 일반 정수형과 같이 연산을 할 수도 있다.

<코드 3>

#include <iostream>

#include <safeint.h>

 

using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

          

           unsigned int X2 = 123;

           SafeInt<unsigned int> X3 = X1 * X2;

          

           getchar();

           return 0;

}


 

이번 회에는 간단하게 정수 연산 시의 오버플로우 문제와 SafeInt가 무엇인지, SafeInt의 간단한 사용 방법만 설명하였습니다.

다음 회는 <코드 2>에서 발생하는 예외를 어떻게 처리하는지 설명하겠습니다.

 

저작자 표시
신고

앞서 C++/CLI로 만든 클래스 라이브러리를 사용하여 아주 간단한 콘솔 환경의 서버 프로그램을 만들어 보겠습니다.

 

C#에서 Console 프로젝트를 생성합니다.

생성 후 솔루션 탐색기에서 참조에 클래스 라이브러리를 추가합니다.


 

HalfNetworkNET 클래스를 상속 받는 클래스를 생성합니다. 저는 HFServer라는 클래스를 만들었습니다.

그런 후 상속 받은 HalfNetworkNET 클래스의 일부 함수를 재정의 합니다.


 

이제 Main 부분만 남았습니다.

namespace TestServer_HFNetworkNET

{

    class Program

    {

        static void Main(string[] args)

        {

            HFServer Server = new HFServer();

 

            // 서버 초기화 및 설정 후 Port를 연다

Server.Init();

            short nRet = Server.Open();

 

            if (HalfNetwork_NET.ERRORCODE.NONE != nRet)

            {

                Console.WriteLine("Failed Server Open!");

                return;

            }

 

            Console.WriteLine("Server Start !!!");

 

            // 네트웍 처리를 한다.

            while (true)

            {

                Server.Run();

            }

 

            Console.WriteLine("End Server");

 

            // 서버 종료 처리를 한다

            Server.End();

        }

    }

}

 

 

이것으로 아주 간단한 Echo 서버가 만들어졌습니다.

자세한 코드는 첨부된 프로젝트를 보시기 바랍니다.

 

이 프로그램을 실행하면 아래와 같습니다.



프로그램이 올바르게 실행되는지 테스트 하기 위해서는 HalfNetwork 솔루션에 있는 TestClient 프로젝트를 빌드한 후 실행하면 됩니다.

오른쪽이 클라이언트 입니다. 여기서는 10개를 접속 후 0.5초마다 패킷을 보내면 서버(왼쪽)는 받은 데이터를 그대로 보냅니다.


종료하면 종료된 세션을 콘솔에 출력합니다.


닷넷 플랫폼에서 서버 프로그램을 만드는 경우 닷넷 프레임웍에서 제공하는 API를 사용해도 좋지만 기존에 만들어져 편리하고 고성능 라이브러리를 사용하는 것도 아주 괜찮은 선택입니다.

C++로 만들어진 라이브러리를 C++/CLI를 사용하여 필요한 부분만 랩핑하여 클래스를 만들면 닷넷에서 마음껏 사용할 수 있습니다.


 

 

저작자 표시
신고