블로그 이미지
차세대 개발 플랫폼인 .NET Framework 4.0 과 Visual Studio 2010 의 정보와 아티클을 제공하는 공식 팀 블로그 입니다. 엄준일(땡초)
« 2010/03 »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      


 
 

nullptr

Language Development/C++0x | 2010/01/28 09:00 | Posted by 흥배

오랜만에 팀 블로그에 C++0x 관련 글을 올립니다.

이미 알고 계시겠지만 Visual Stuido 2010 Beta2에 새로운 C++0x 기능이 추가 되었습니다.

추가된 것은 nullptr 이라는 키워드 입니다.

nullptr C++0x에서 추가된 키워드로 널 포인터(Null Pointer)를 나타냅니다.

 

 

null_ptr이 필요한 이유

 

C++03까지는 널 포인터를 나타내기 위해서는 NULL 매크로나 상수 0을 사용하였습니다.

그러나 NULL 매크로나 상수 0을 사용하여 함수에 인자로 넘기는 경우 int 타입으로 추론되어 버리는 문제가 발생 합니다.

 

< List 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;

}

 

< 결과 >

 


첫 번째 func 호출에서는 double* 로 캐스팅을 해서 의도하는 func이 호출 되었습니다. 그러나 두 번째와 세 번째 func 호출의 경우 func( doube* p ) 함수에 널 포인터로 파라미터로 넘기려고 했는데 의도하지 않게 컴파일러는 int로 추론하여 func( int a )가 호출 되었습니다.

 

바로 이와 같은 문제를 해결하기 위해서 nullptr 이라는 키워드가 생겼습니다.

 

 

 

nullptr 구현안

 

C++0x에서 nullptr의 드래프트 문서를 보면 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 = {};

 

 

 

nullptr 사용 방법

 

사용방법은 너무 너무 간단합니다. ^^

그냥 예전에 널 포인터로 0 이나 NULL을 사용하던 것을 그대로 대처하면 됩니다.

 

char* p = nullptr;

 

<List1>에서 널 포인트를 파라미터로 넘겨서 func( double* p )가 호출하게 하기 위해서는

func( nullptr );

로 호출하면 됩니다.

 



nullptr의 올바른 사용과 틀린 사용 예

 

 

올바른 사용

char* ch = nullptr; // ch에 널 포인터 대입.

sizeof( nullptr ); // 사용 할 수 있습니다. 참고로 크기는 4 입니다.

typeid( nullptr ); // 사용할 수 있습니다.

throw nullptr; // 사용할 수 있습니다.

 

 

틀린 사용

int n = nullptr; // int에는 숫자만 대입가능한데 nullptr은 클래스이므로 안됩니다.

 

Int n2 = 0

if( n2 == nullptr ); // 에러

 

if( nullptr ); // 에러

 

if( nullptr == 0 ); // 에러

 

nullptr = 0; // 에러

 

nullptr + 2; // 에러

 

 

 

nullptr 너무 간단하죠? ^^

VC++ 10에서는 예전처럼 널 포인터를 나타내기 위해서 0 이나 NULL 매크로를 사용하지 말고 꼭 nullptr을 사용하여 함수나 템플릿에서 널 포인터 추론이 올바르게 되어 C++을 더 효율적으로 사용하기 바랍니다.^^

 

 

 

짜투리 이야기...... ^^


왜 nullptr 이라고 이름을 지었을까?

nullptr을 만들 때 기존의 라이브러리들과 이름 충돌을 최대한 피하기 위해서 구글로 검색을 해보니 nullptr로 검색 결과가 나오는 것이 별로 없어서 nullptr로 했다고 합니다.

제안자 중 한 명인 Herb Sutter은 현재 Microsoft에서 근무하고 있는데 그래서인지 C++/CLI에서는 이미 nullptr 키워드를 지원하고 있습니다.

 

 

C++0x 이야기

근래에 Boost 라이브러리의 thread 라이브러리가 C++0x에 채택 되었다고 합니다. Boost에 있는 많은 라이브러리가 C++0x에 채택되고 있으므로 컴파일러에서 아직 지원하지 않는 C++0x의 기능을 먼저 사용해 보고 싶다면 꼭 Boost 라이브러리를 사용해 보기 바랍니다.

 


 

참고

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

 

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'Language Development > C++0x' 카테고리의 다른 글

nullptr  (0) 2010/01/28
대용량 파일 조작을 위한 C++0x의 변화  (0) 2009/09/07
[VC++] 14. decltype  (0) 2009/06/30
[VC++] 13. Lambda - 네 번째  (1) 2009/06/23
[VC++] 12. Lambda - 세 번째  (0) 2009/06/16
[VC++] 11. Lambda - 두 번째  (0) 2009/06/09

C++0x와 관련된 글을 6 30일 이후로 처음 올립니다.

그 동안 C++0x에 관심이 없다던가 전달할 것이 없는 것은 아니었지만 근래에는 Parallel에 집중하고 있어서 글을 올릴 수가 없었습니다.

 

현재까지 공개된 C++0x를 보면 여러가지 다양한 기능이 추가 되었습니다만 현재 VC++ 10 Beta1에는 핵심 기능과 그 외 자잘한 몇가지 것들만 구현되어 있습니다. 다음 Beta2나 정식 버전이 나오면 더 많은 것들이 구현 되어 있으리라 생각합니다.

 

제가 자주 가는 C++ 관련된 좋은 글이 올라온 사이트에서 본 C++0x에서 추가된 새로운 기능을 하나 소개하려고 합니다.

 

 


지금은 테라바이트 시대


제 친구 중 가장 먼저 PC를 구입했을 때가 90년대 초반으로 IBM PC XT 제품으로 하드 디스크 용량이 80 메가 였고, 95년에 처음 제 PC를 구입할 때는 인텔 486 DX 칩을 사용한 제품으로 그 당시 일반적인 하드 디스크 용량은 256 메가였습니다. 95년에 처음 PC를 구입할 때는 정말 엄청나게 크다고 생각했었습니다.^^

그런데 요즘 PC를 구입하시는 분들은 테라바이트 단위의 하드 디스크를 장착을 하고 있더군요.

 

 

C++이 나왔을 때는


C++은 꽤 오래 전에 만들어진 언어입니다. 95년 보다도 더 이전에 나왔습니다

ㅊC+++이 처음 만들어졌을 때나 처음 표준이 완성 되었을 때를 지금과 비교하면 보통 사용하는 파일의 크기는 대부분 작은 편이었습니다.

 

ㅇ아마 저처럼 90년대 중반 이전에 PC를 사용한 분들은 1기가 이상의 파일을 사용할 것이라고는 생각하지 못했을 겁니다(참고로 90년대에 PC 통신으로는 1메가 다운로드도 무척 긴 시간이 걸렸습니다^^).

ㄱ그래서 C++ I/O Stream에서는 int long 타입으로 파일의 위치를 조작하는 것을 생각하고 만들어져 있습니다. 하지만 요즘은 하나의 파일이 워낙 큰 용량을 차지하고 있어서 문제가 됩니다.

 

 

 

C++0x에서 대용량 파일의 위치 조작하기


현재 C++ I/O Stream에서는 아직 long long 타입이 들어가 있지 않으므로 int long 타입의 크기를 넘어서는 파일의 위치 조작에는 문제가 있습니다.

그러나 C++0x에서는 N2926의 제안으로 이 문제가 해결 되었습니다.

 

 

변경점으로는

 

char_traits 에서는

현재

C++0x

typedef OFF_T off_type;

typedef unspecified-type off_type;

typedef POS_T pos_type;

typedef unspecified-type pos_type;

 

<ios> 에서는

현재

C++0x

typedef OFF_T streamoff;

typedef implementation-defined streamoff;
typedef POS_T streamsize;
typedef implementation-defined streamsize;

 로 변경 되었습니다.

 

 


VC++ 10 Beta1에서도 사용할 수 있습니다.

 

서두에 C++0x에서는 공개된 기능이지만 VC++ 10 Beta1에서는 구현되어 있지 않는 것들이 있다고 했는데 지금 소개한 것은 구현되어 있습니다.

지금 당장 테스트 해 보세요 ^^



 

 

ps.1 : 위 글은 여기 올라온 글을 다듬은 것입니다. 예제코드는 따로 파일로 올립니다.

      http://d.hatena.ne.jp/faith_and_brave/20090904/1252051994


ps.2 : 팀 블로그에 올기기 애매한 C++0x에 대한 글을 제 블로그에 가끔씩 올리고 있으니 틈날 때 와서 보세요^^

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'Language Development > C++0x' 카테고리의 다른 글

nullptr  (0) 2010/01/28
대용량 파일 조작을 위한 C++0x의 변화  (0) 2009/09/07
[VC++] 14. decltype  (0) 2009/06/30
[VC++] 13. Lambda - 네 번째  (1) 2009/06/23
[VC++] 12. Lambda - 세 번째  (0) 2009/06/16
[VC++] 11. Lambda - 두 번째  (0) 2009/06/09

[VC++] 14. decltype

Language Development/C++0x | 2009/06/30 22:00 | Posted by 흥배
VSTS 2010 Beta1에 추가된 C++0x의 decltype에 대해서 아주 아주 짧게 이야기하려고 합니다.^^

템플릿 형(type)이랑 템플릿 메타 프로그래밍 등에서 함수의 반환 형이 복잡하게 정의 되어 있는 경우 형을 단순하게 기술할 수 없습니다.

C++0x에서는 이런 문제를 풀기 위해 auto decktype 두 개를 제공합니다.

auto는 이미 앞에서 제가 설명했으니 기억나지 않은 분들은 다시봐 주세요^^

 

decltype은 auto 처럼 식의 형을 컴파일 할 때 결정할 수 있습니다.


사용 예는 아래와 같습니다.

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로 됩니다.



또한 함수의 반환형에도 사용할 수 있습니다.

int foo();

decltype(foo()) value;


decltype(foo()) value;는
int value;로 됩니다.




처음 말했듯이 decltype에 대해서는 이렇게 아주 짧게 이야기를 마치겠습니다. ㅎㅎ

만약 decltype에 대해서 좀 더 자세하게 알고 싶다면 VC++ 팀의 블로그에 있는 글을 추천합니다.
http://blogs.msdn.com/vcblog/archive/2009/04/22/decltype-c-0x-features-in-vc10-part-3.aspx

 
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'Language Development > C++0x' 카테고리의 다른 글

nullptr  (0) 2010/01/28
대용량 파일 조작을 위한 C++0x의 변화  (0) 2009/09/07
[VC++] 14. decltype  (0) 2009/06/30
[VC++] 13. Lambda - 네 번째  (1) 2009/06/23
[VC++] 12. Lambda - 세 번째  (0) 2009/06/16
[VC++] 11. Lambda - 두 번째  (0) 2009/06/09

[VC++] 13. Lambda - 네 번째

Language Development/C++0x | 2009/06/23 08:00 | Posted by 흥배

클로져 사용하기 2

 

<Code 4> 에서는(세 번째 글의 마지막 예제) 하나의 변수만을 캡쳐했지만 복수의 변수를 캡쳐하는 것도 가능할까요? 네 당연히 가능합니다.


‘[]’ 사이에 캡쳐할 변수를 선언하면 됩니다.

[ &Numb1, &Numb2 ]

 

그럼 ‘[&]’로 하면 어떻게 될까요? 이렇게 하면 람다 식을 정의한 범위 내에 있는 모든 변수를 캡쳐할 수 있습니다.

 

< Code 6. >

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;

}

 

< 결과 >






람다 식을 STL 컨테이너에 저장

 

람식 식을 STLfunction을 사용하여 STL 컨테이너에 저장을 할 수 있습니다.

아래는 int를 반환하는 람다 식을 vector에 저장하여 사용하는 것입니다.

 

< Code 6. >

#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;

}

 

< 결과 >






람다 식에서 클래스의 멤버 함수 호출

 

클래스의 멤버 함수 내에 람다 식을 정의하고, 이 람다 식에서 해당 클래스의 멤버를 호출 할 수 있습니다. 클래스 멤버 내의 람다 식은 해당 클래스에서는 friend로 인식하므로 람다 식에서 private 멤버의 접근도 할 수 있습ㄴ다.

그리고 클래스의 멤버를 호출할 때는 ‘this’를 캡쳐합니다.

 

< Code 7. >

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;

}

 

< 결과 >



<Code 7> Network 클래스의 멤버 함수 AllSend()에서 람다 식을 사용했습니다.

 for_each(SendPackets.begin(), SendPackets.end(), [this](int i){ Send(i); });

보시다시피 ‘[]’ 사이에 ‘this’를 기술하였고, Network 클래스의 private 멤버 함수인 Send를 호출하고 있습니다. 당연히 멤버 변수도 호출 할 수 있습니다.

 

 

 

람다 식에서의 재귀

 

람다는 재귀도 가능합니다.


< Code 8. >

int main()

{

    function<int(int)> fact = [&fact](int x) {

        return x == 0 ? 1 : x * fact(x - 1);

    };

 

    cout << fact(3) << endl;

}

 

fact변수를 참조로 넘겨서 재귀를 하고 있습니다.

 

출처 : http://d.hatena.ne.jp/faith_and_brave/20081119/1227087326

 

 

 

auto와 람다

 

람다 식을 auto에 대입하여 사용할 수 있습니다.

< Code 7>에서 람다를 사용했던

for_each(SendPackets.begin(), SendPackets.end(), [this](int i){ Send(i); });

auto 사용하여 바꾸면 아래와 같이 됩니다.

 

auto myLambdaFunc = [this]( int i ) { this->Send(i); };

for_each(SendPackets.begin(), SendPackets.end(), myLambdaFunc );

 

람다를 폭 넓게 사용할 때 auto를 같이 사용하면 간결한 코드를 만들 수 있을 것 같습니다.

 

 


이것으로 람다에 관한 이야기를 마무리하려고 합니다.

부족함이 많은 글이지만 C++0x의 람다에 관한 내용은 대부분 다 소개했다고 생각합니다.

다만 아직 람다는 더 깊이 팔 수 있는 것입니다.^^;

그래서 다음에 또 기회가 된다면 이번보다 좀 더 깊은 내용 소개하도록 하겠습니다.

 

원래는 람다를 끝으로 'Concurrency Runtime'에 대한 것을 설명하려고 했는데 VC++ 10에 추가된 C++0x 기능 중 'decltype'이라는 것이 있어서 이것까지 하고 난 후 들어가려고 합니다.

그러니 다음 주에 올릴 'decltype'도 기대해 주세요^^


저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'Language Development > C++0x' 카테고리의 다른 글

대용량 파일 조작을 위한 C++0x의 변화  (0) 2009/09/07
[VC++] 14. decltype  (0) 2009/06/30
[VC++] 13. Lambda - 네 번째  (1) 2009/06/23
[VC++] 12. Lambda - 세 번째  (0) 2009/06/16
[VC++] 11. Lambda - 두 번째  (0) 2009/06/09
[VC++] 9. Lambda ( 람다 ) - 첫 번째  (4) 2009/06/02

[VC++] 12. Lambda - 세 번째

Language Development/C++0x | 2009/06/16 21:58 | Posted by 흥배

클로져 사용하기 1

 

그럼 이제 클러져에 대해서 알아 보겠습니다.

람다를 사용할 때 람다 식 외부에 정의되어 있는 변수를 람다 식 내에서 사용한 후 결과를 그 변수에 그대로 저장하고 싶을 때가 있습니다. 이럴 때 클로져를 사용하면 됩니다.

클로져는 참조나 복사로 전달이 가능합니다. 참조를 사용하는 경우는 &을 사용하면 됩니다.

람다 표현의

  

[](파라메터) { }

에서 앞의 ‘[]’ 사이에 클로져할 변수를 기술하면 됩니다.

 

< Code 4. >

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;

}

 

< 결과 >


람다 식이 외부에 있는 TotalMoney1 변수를 참조로 캡쳐(capture)(람다 식의 외부에 있는 것을 사용할 때 캡쳐한다고 합니다) 하여 Moneys 벡터에 있는 값을 모두 더하고 있습니다.

 

그럼 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

 


그럼 포인터 전달은 어떨까요?

< Code 5. >

………….

int TotalMoney2 = 0;

int* pTotalMoney2 = &TotalMoney2;

for_each(Moneys.begin(), Moneys.end(), [pTotalMoney2](int Money) {

             *pTotalMoney2 += Money;

           });

          

cout << "Total Money 2 : " << TotalMoney2 << endl;

 ………..

 

< 결과 >


당연히 잘 됩니다.^^




클로져에 대한 이야기가 끝난 것은 아닙니다. 다음 회에 나머지 이야기를 다 할테니 기다려 주세요 ~



저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

[VC++] 11. Lambda - 두 번째

Language Development/C++0x | 2009/06/09 22:00 | Posted by 흥배

C++에서의 람다 사용 법

 

람다 사용 방법은 아래와 같습니다.

[](파라메터) { }

 

int 값에 50을 더한 후 반환하는 람다 함수 

[](int x) { return x + 50; }

 

위 방법은 반환 값의 type“x+ 50”의 값의 type으로 추정되어 반환됩니다.

만약 반환 값 type을 지정하고 싶다면

 

[](int x) -> int { return x + 50; }

라고 하면 됩니다. 그리고 참고로 반환하지 않을 것이면 반환하지 않아도 됩니다.

또한 클로져를 사용하면 “[]” 사이에 참조를 넘길 수도 있습니다.

 

그럼 람다 사용 방법을 좀 더 알기 쉽도록 여러 가지 사용 예를 보여 드리겠습니다.

 

 

 

STL find_if에서 람다 사용

 

그럼 <Code 2>(첫  번째 글)에서 펑터를 정의하여 사용했던 것을 람다를 사용하는 것으로 바꾸어 보겠습니다.

 

< Code 3. >

#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;

}

 

< 결과 >



find_if 알고리즘을 사용하여 죽은 유저를 찾기 위해서 <Code 2>에서는 펑터를 정의한 후 사용하였지만

struct FindDieUser

{

           bool operator()  (User& tUser) const { return tUser.IsDie(); }

};

Iter = find_if( Users.begin(), Users.end(), FindDieUser() );


< Code 3>는 람다를 사용하여 한 줄로 간단하게 끝내버렸습니다.

Iter = find_if( Users.begin(), Users.end(), [](User& tUser) -> bool { return true == tUser.IsDie(); } );

 

람다 덕분에 STL의 알고리즘 사용이 아주 편리해졌습니다.

 

람다가 생겨서 이전과 다르게 C++의 표현력이 이전보다 훨씬 더 좋아졌습니다.

참고로 VC++ 10에서는 병렬 패턴 프로그래밍(PPL) 이라는 것이 새로 추가 되었습니다. 이 라이브러리는 템플릿으로 만들어진 것으로 람다가 많이 사용 되었다고 합니다.

( PPL에 대해서는 정재원님이 포스팅하고 있는 2008 PDC에서의 PPL 강연 동영상을 꼭 보시기를 권합니다. 그리고 저도 곧 PPL 관련 글을 포스팅할 예정입니다. )


이번 회는 간단하게 C++에서 람다를 어떻게 사용하는지 이야기했습니다. 다음에는 클로져 사용에 대해서 이야기 하겠습니다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

이번은 C++0x에서의 새로운 기능 중의 하나인 Lambda에서 대해서 설명하겠습니다.

C#이나 동적 프로그래밍 언어를 공부 하신 분들은 Lambda에 대해서 들어보셨을 것입니다.

 

람다를 잘 모르는 분들을 위해서 현재 가장 쉽게 Lambda를 접할 수 있는 C#을 통해서 Lambda 사용 예를 들어보겠습니다.

(C#을 모르고 C++만 아는 분들이라도 무리 없이 볼 수 있으니 그냥 넘어가지 마시고 봐 주세요. C# 이야기는 조금만 할께요^^)

 

 

 

C#에서의 람다 식

 

람다 식은 식과 문을 포함하여 대리자나 식 트리 형식을 만드는데 사용할 수 있는 익명함수입니다.

 

형식은 다음과 같습니다.  

입력 매개 변수 => or

 

람다 식은 주로 어떤 라이브러리의 식과 결합해서 사용할 식을 만들 때 사용합니다.

람다 식이 없다면 다른 식과 결합하기 위해서는 따로 함수를 만들어서 사용해야 되므로 거추장스러워 지는데 람다 식을 사용하면 아주 간단하게 구현할 수 있습니다.

 

< Code 1. C# - 이름 중 문자 길이가 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);

     }

}

 

<Code 1>을 보면 람다 식을 사용하면 한 줄로 끝나는 것을 람다 식을 사용하지 않으면 List 컨테이너를 생성하고 foreach 구문을 사용하여 이름을 하나씩 조사하여 List 컨테이너에 추가해야 되는 코드가 필요해집니다.

 

C#에서 람다 식이 가장 유용하게 사용되는 부분은 Linq 일겁니다.

만약 Linq에서 람다 식을 사용하지 않게 된다면…Linq 사용하기가 꽤 힘들어지겠죠

 

 

 

C++에서 STL 알고리즘을 사용했던 불편했던 것

 

기존의 C++에서 STL find_if, sort 등의 알고리즘을 사용할 때 특정 조건자를 사용하기 위해서는 펑터(functer)를 만들어야 합니다. 그런데 이것 때문에 따로 펑터를 만드는 것이 귀찮아서 보통 그냥 따로 함수를 만들어서 구현하기도 합니다.

 

< Code 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;

}

 

<Code 2>는 유저들 중 죽은 유저를 찾기 위해 find_if 알고리즘을 사용했는데 이것 때문에 struct로 펑터를 만들어야 됩니다. 궂이 펑터까지 만들지 않고 그냥 간단하게 기술하면 좋겠죠.

혹시 <Code 1>에서 C#에서는 람다 식으로 쉽게 구현했던 것을 보니 <Code > find_if 알고리즘도 이런 것을 사용하면 좋겠죠? ^^

 

그래서 C++0x에서 람다가 새로 생겼습니다.

 

 

 

람다란 ?

 

먼저 람다와 관련된 용어를 정의하겠습니다.

 

Lambda

람다 식 또는 람다 함수라고 부르며 무명 함수 오브젝트를 가리키는 식입니다.

 

Closure

클로져 : 람다 식이 컴파일러 의해 평가된 결과로 자동적으로 생성되는 무명 함수 오브젝트.

클로져는 람다 함수를 정의함 코드 내에서 참조된 변수가 무명 함수 오브젝트의 멤버 변수로서 유지 되던가 아니면 람다 함수가 생성된 프레임 포인터가 무명 함수 오브젝트 내에 유지 되는 것을 의미합니다.

 

람다 함수의 무엇을 말하는지 좀 알겠는데 클로져는 약간 알쏭달쏭 하시죠? 걱정 마세요. 앞으로 나올 설명과 코드를 보면 쉽게 이해할 수 있습니다.^^




이번은 C#을 통해서 람다라는 것이 어떤 것인지 살짝 소개하고, C++에서 STL 알고리즘에서 펑터를 사용할 때의 불편함 점, 람다 용어 설명으로 마치겠습니다.

C++0X에서 람다를 어떻게 사용하는지는 다음 글에서 본격적으로 설명하겠습니다.


글 하나에 너무 많은 글을 적으면 잘 보지 않을 것 같아서 이번 글에서는 일단 덮밥만 뿌려놓겠습니다. ^^;;;

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

RValue Reference에 관한 글의 마지막으로 Perfect Forwarding에 대해서 설명합니다.

 

 

인수 전달 문제

 

< Code 1. >

void inner(int& i)

{

}

 

template <typename T> void outer(T& t) {

           inner(t);

}

 

 

int main()

{

           int a = 5;

           outer(a);

 

           outer(6);

           return 0;

}


<Code 1>을 컴파일 하면 outer(6)에서 컴파일 에러가 발생합니다.

error C2664: 'outer' : cannot convert parameter 1 from 'int' to 'int &'라는 에러 결과가 출력됩니다. <Code 1>을 컴파일 하기 위해서 파라메터에 const를 포함하는 함수를 재정의 해야 합니다.

 


< Code 2. >

void inner(const int& i)

{

}

template <typename T> void outer(const T& t) {

           inner(t);

}

 

<Code 2>에서 정의한 함수를 <Code 1>에 추가하면 <Code 1>은 컴파일 할 수 있습니다.

하지만 함수의 파라메터의 개수가 N개이고 파라메터 중 const를 포함하는 것의 위치가 제각각 이라면 재정의 해야 할 함수가 지수적으로 증가합니다.

그러나 ‘&&’을 사용하면 이 문제를 해결 할 수 있습니다.

 


< Code 3. >

void inner(int& i)

{

}

 

template <typename T> void outer(T&& t) {

           inner(t);

}

 

int main()

{

           int a = 5;

           outer(a);

 

           outer(6);

           return 0;

}

 

 


 

std::forward

 

< Code 4. >

class MyTest

{

public:

           MyTest(int& N) {}

};

 

template <typename T1, typename T2>

std::shared_ptr<T1>

factory(T2&& t2)

{

    return std::shared_ptr<T1>( new T1( t2 ) );

}

 

int main()

{

           std::shared_ptr<MyTest> p1 = factory<MyTest>(5);

          

           int value = 5;

           std::shared_ptr<MyTest> p2 = factory<MyTest>(value);

 

           return 0;

}

 

<Code 4>에서도 <Code 3>과 같이 RValue Reference에 의해

std::shared_ptr<MyTest> p1 = factory<MyTest>(5);

에서 컴파일 에러가 발생하는 것을 해결했습니다.

 

그런데 이것은 MyTest(int& N) 에서 LValue 전달 받기를 원하지만 ‘&&’ 의해서 RValue 전달이 되어버려 Perfect Forwarding이 파괴되어 버렸습니다.

이런 문제를 해결하기 위해서는 std::forward를 사용하면 됩니다.

 

< Code 5. >

template <typename T1, typename T2>

std::shared_ptr<T1>

factory(T2&& t2)

{

    return std::shared_ptr<T1>( new T1( std::forward<T2>(t2) ) );

}

 

Factory 함수를 <Code 5>와 같이 바꾼 후 컴파일을 하면

MyTest::MyTest(int &)' : cannot convert parameter 1 from 'int' to 'int &'

라는 에러가 출력됩니다.

 

 

 

이것으로 생각외로 길었던 RValue Reference에 대한 글을 마칩니다.


서두에 이야기 했듯이 RValue Reference C++에 있어서 아주 유용한 것이지만 정확하게 이해하기가 쉽지 않습니다. Microsoft VC++ 팀에서도 우선은 RValue Reference의 사용 패턴에 대해서 이해하도록 권하고 있습니다. 그래서 저도 사용 패턴에 맞추어 글을 적었습니다.


RValue Reference에 대해서 자세하게 알고 싶은 분들은 앞선 글에서 소개했던 사이트를 다시 소개 해 드릴 테니 꼭 읽어보시기 바랍니다.

 

 

 

참고 사이트

1. Rvalue References: C++0x Features in VC10, Part 2

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

 

2. A Brief Introduction to Rvalue References

http://www.artima.com/cppsource/rvalue.html


저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

STL RValue Reference

 

앞선 글에서 C++0x STL에는 RValue Reference가 적용되었다고 말했습니다.

이 덕분에 기존의 코드를 수정하지 않고 C++0x의 컴파일러로 빌드만 해도 프로그램의 성능이 좋아집니다.

 

 

RValue Reference가 적용된 C++0x STL vector

VC++ 10 vector쪽 소스 코드를 보면 이전의 vector에는 없던 코드가 있습니다.

< Code 1. STL vector의 소스 중 일부 >

……………………

#if _HAS_RVALUE_REFERENCES

 

                  vector(_Myt&& _Right)

                                   : _Mybase(_Right._Alval)

                  {                // construct by moving _Right

                     _Assign_rv(_STD forward<_Myt>(_Right));

                  }

………

 

<Code 1>의 내용을 보면 바로 아시겠죠? Move 생성자가 정의 되어 있습니다.

< Code 2. >

vector<int> foo()

{

        vector<int> v;

        v.push_back(1);

        v.push_back(2);

        return v;

}

 

int main()

{

        vector<int> v1 = foo();

        cout << v1[0] << endl;

        return 0;

}

 

<Code 2> VC++9(visual Studio 2008) VC++ 10(Visual Studio 2010 CTP)에서 컴파일 해서 실행하면 vector의 생성자에서 서로 다른 부분을 호출하는 것을 볼 수 있습니다.

 

VC++ 9에서 <Code 2> 디버깅

VC++ 9에서 디버깅을 해보면 다음의 위치에서 브레이크 포인터가 걸립니다.

 

위 코드를 보면 복사 생성자에서 받은 vector의 크기를 비교하여 현재 공간이 인자로 받은 vector보다 작으면 재할당을 하고, vector에 있는 모든 요소를 복사합니다.

 

VC++ 10에서 <Code 2> 디버깅

 

보시는 바와 같이 VC++ 10에서는 Move 생성자가 호출됩니다. 그리고 요소를 복사하지 않고 메모리 상의 이동을 합니다.

크기가 작은 vector라면 성능상의 차이는 별 의미가 없겠지만 크기가 큰 vector라면 무시하지 못할 차이가 납니다.

또한 vector에 할당된 공간을 다 사용한 상태에서 새로운 요소를 삽입하면( vector.push_bask() ) 기존에는 새로운 공간을 할당 후 저장하고 있던 모든 요소를 복사를 하지만 C++0x에서는 Move Semantics에 의해 메모리 상의 이동을 합니다.

 

STL string

 너무 당연하지만 string 클래스도 RValue Reference가 잘 적용되어 있습니다.

< Code 3. string 결합>

string msg1(“Error”);

string msg2(“- Network”);

string msg3(“: Accept”);

 

string Msg = msg1 + “ “ + msg2 + “ “ + msg3;

 

<Code 3>에 만약 RValue Reference가 적용되지 않았다면 operator+()가 호출될 때마다 임시 문자열이 생성되어 동정 메모리 할당과 복사가 일어나지만 RValue Reference을 적용하면 불 필요한 동적 메모리 할당과 복사 처리를 제거할 수 있습니다.

 

 

std::move()를 사용할 때 주의할 점

 std::move()를 사용하면 Move Semantics에 의해 의도하지 않은 버그가 발생할 수 있습니다.

 < Code 4. >

int main()

{

           vector<int> v1;

           v1.push_back(10);

           v1.push_back(12);

 

           vector<int> v2 = std::move(v1);

          

           cout << v1.size() << endl;

           cout << v2.size() << endl;

 

           return 0;

}

 

< 결과 >


<Code 4>에서 v1 v2에서 Move 생성자를 사용하기 위해서 std::move()를 사용했습니다.

std::move는 메모리 이동을 한다고 했습니다. 그래서

          vector<int> v2 = std::move(v1);

후에는 v1의 크기는 0이 됩니다.

 

 

이것으로 RValue Reference에서의 Move Semantics에 대한 이야기는 일단락합니다.

제가 너무 복잡하지 않게 설명하기 위해 세세한 부분에 대한 것은 설명하지 않았으니 좀 더 정확하게 알고 싶은 분들은

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

의 글을 꼭 보시기 바랍니다.

 

 

다음에는 Perfect Forwarding에 대하여 이야기로 우측참조에 대한 글을 끝맺겠습니다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

이번 회는 Move 생성자와 Move 대입 연산자를 정의 했을 때 어떤 결과가 나오는지, 그리고 std::move 라는 것에 대해서 이야기 하겠습니다.



복사 생성자와 대입 연산자만 정의했을 때


< Code 1. 복사 생성자와 대입 연산자 정의 >

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;

           }

};

 

int main()

{

           NPC npc1( NPC( 10,"Orge1") );

          

           NPC npc2(11,"Orge2");

           NPC npc3 = npc2;

          

           NPC npc4;

           NPC npc5;

           npc5 = npc4;

 

           return 0;

}

 

< 결과 >


당연한 결과가 나왔죠^^

 

그럼 <Code 1> Move 생성자와 Move 대입 연산자를 정의하고 결과를 보겠습니다.

 

 

 

Move 생성자와 Move 대입 연산자 정의

 

< Code 2. Move 생성자와 Move 대입 연산자 정의>

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;

 

           cout <<  endl << "4" << endl;

           NPC npc6 = NPC(12, "Orge3");

 

           cout <<  endl << "5" << endl;

           NPC npc7;         NPC npc8;

           npc8 = std::move(npc7);

 

           return 0;

}

 

< 결과 >


 

아래 코드는 Move 생성자를 정의해서 예전에는 복사 생성자가 호출되었지만 이제 ‘Move 생성자가 호출 됩니다.

NPC npc1( NPC( 10,"Orge1") );

 

 

 

std::move

 

NPC npc2;          NPC npc3;

npc3 = npc2;

에서는 대입 연산자가 호출되는데 이것을 ‘Move 대입 연산자’가 호출되도록 하기 위해서 C++0x에서 새로 생긴 std::move()를 사용합니다.


NPC npc7;         NPC npc8;

npc8 = std::move(npc7);


std::move Move Semantics를 위해 이번에 새롭게 추가된 것입니다.

이것은 좌측 값(LValue)을 우측 값(RValue)으로 변환하기 위한 함수입니다.

 

std::move는 RValue Reference한 오브젝트의 멤버를 이동할 때도 사용합니다.

NPC( NPC&& npc) : Name(std::move(npc.Name)), NPCCode(std::move(npc.NPCCode))

{

   ………

}

 


std::move <utility>에 정의 되어 있습니다.


namespace std {

    template <class T>

    inline typename remove_reference<T>::type&& move(T&& x)

    {

        return x;

    }

}

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License