Visual Studio 2010 공식 팀 블로그 @vsts2010

Posted by 방수철(마루하)


안녕하세요. 이번 글에서는 unique_ptr에 대해서 소개드리겠습니다.

Remind

주요 변경 사항으로 소개되 내용에서, unique_ptr에 대해서 다음과 같이 설명하고 있습니다.

"auto_ptr 클래스보다 더 안전한 스마트 포인터형인 unique_ptr 클래스의 구현에도 Rvalue reference가 사용되었습니다. unique_ptr 클래스는 move는 할 수 있지만 copy는 불가능하며, safety에 영향을 미치지 않으면서 강한 소유 의미(strict ownership semantics)를 구현했습니다. 또한, unique_ptr 클래스는 rvalue references가 구현된 container들과 잘 동작합니다."

unique_ptr in MSDN Library

MSDN Library에 소개된 unique_ptr class에 관한 설명입니다.

- 소유하는 객체에 대한 포인터를 저장한다. 해당 객체는 다른
unique_ptr에 의해서 소유될 수 없다. 해당 객체는 unique_ptr이 해제될 때에 해제된다. 다음은 MSDN에 게시되어 있는 class 정의 코드입니다.

  1 template<class Type, class Del = default_delete<Type> >
  2     
class unique_ptr {
  3
  4
public:
  5         
typedef Type element_type;
  6         
typedef Del deleter_type;
  7         
typedef T1 pointer;
  8
  9         
unique_ptr ();
 10         
unique_ptr (
 11             
nullptr_t _Nptr
 12         
);
 13         
explicit unique_ptr (
 14             
pointer _Ptr
 15         
);
 16         
unique_ptr (
 17             
pointer _Ptr,
 18             
typename conditional<
 19                 
is_reference<Del>::value, 
 20                 
Del,
 21                 
typename add_reference<const Del>::type
 22             
>::type _Deleter
 23         
);
 24         
unique_ptr (
 25             
pointer _Ptr,
 26             
typename remove_reference<Del>::type&& _Deleter
 27         
);
 28         
unique_ptr (
 29             
unique_ptr&& _Right
 30         
);
 31         
template<class Type2, Class Del2>
 32             
unique_ptr (
 33                 
unique_ptr<Type2, Del2>&& _Right
 34             
);
 35
 36     
~unique_ptr ();
 37
 38     
unique_ptr& operator= (
 39         
unique_ptr&& _Right
 40     
);
 41     
template<class Type2, Class Del2>
 42         
unique_ptr& operator= (
 43             
unique_ptr<Type2, Del2>&& _Right
 44         
);
 45     
void swap (
 46         
unique_ptr& _Right
 47     
);
 48     
pointer release ();
 49     
void reset (
 50        
pointer _Ptr = pointer()
 51     
);
 52
 53     
pointer get () const;
 54     
Type& operator* () const;
 55     
pointer operator-> () const;
 56     
Del& get_deleter ();
 57     
const Del& get_deleter () const;
 58     
explicit operator bool () const;
 59
 60     
unique_ptr(
 61         
const unique_ptr& _Right
 62
) = delete;
 63     
unique_ptr& operator=(
 64         
const unique_ptr& _Right
 65
) = delete;
 66
 67
private:
 68     
pointer stored_ptr;    // exposition only
 69     
Del stored_deleter;    // exposition only
 70     
};



왜 auto_ptr은 deprecation이 되었을까?
  C++ 0x에서 auto_ptr은 deprecation으로 결정되었습니다. C++ 0x에서는 최소한 auto_ptr과 같은 효율을 가지며 move semantics를 지워하는 unique_ptr을 추가하게 됩니다. 그렇다면 auto_ptr은 왜 deprecation이 되었을까요? 그 이유에 대해서 설명드리겠습니다.

  역사적으로 이전 버전의 C++ 표준에서, 반복된 수정과 패치를 통해서 주요 확장에서 auto_ptr은 안정성을 확보했습니다. 하지만 설계상의 심각한 결함을 극복하지는 못했습니다. 대표적으로 generic 알고리즘과 auto_ptr을 함께 사용할 때에 문제가 되었습니다. generic 알고리즘에서는 복사 연산 문법은 정말로 copy 연산이 일어난다는 것을 가정합니다. 하지만 auto_ptr에서 복사 연산자는 실제로 move와 같이 동작합니다. 이런 근본적인 차이 때문에 알고리즘의 구현에 따라서 원하는 결과를 얻지 못 할 수도 있었습니다.

 정렬 알고리즘을 통해서 예를 들겠습니다. 버블 정렬이나 선택 정렬에서는 하나의 지역변수에 한 원소를 골라서 복사를 하는 방식을 사용합니다. 이런 경우에는 완벽하게 유효한 구현이 됩니다. 하지만 빠른 정렬의 경우를 생각해보면, 구현 중에 다음과 같은 코드가 있을 겁니다.

  value_type pivot = *mid_point;
 이 경우에 알고리즘상 복사 연산자에서 실제로 복사가 일어남을 전제하는 알고리즘인 겁니다. 하지만 auto_ptr은 복사 표현을 통해서 move를 구현했기 때문에 이런 구현에서는 sort()의 결과가 안전할 것인지 보장 할 수가 없었습니다.

 수년간의 논의 끝에, C++ 표준 위원회는 auto_ptr은 deprecated로 결정했습니다. 하지만 auto_ptr의 다른 모든 장점을 수용할 수 있으며 더 안전하고 명확한 인터페이스를 C++ 0x에 추가 될 필요가 있었고, 그 대채자가 unique_ptr 입니다. unique_ptr은 복사 생성자를 private으로 선언합니다. 따라서 generic 알고리즘을 unique_ptr과 같이 사용하여 호출할 때에, 모든 generic 코드는 컴파일 타임에 에러가 나거나 그렇지 않다면 완벽하게 유효한 결과를 내게 됩니다.

 다음 글에서는 unique_ptr의 예제 코드 위주로 auto_ptr과의 차이점과 개선점을 살펴보도록 하겠습니다.
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

Posted by 방수철(마루하)

소개
안녕하세요. 이번에 팀블로그에 합류하게 된 방수철이라고 합니다.
저는 VC++ 2010에서 구현된 Standard C++ Library에 대해서 소개를 드리고자 합니다.
이 글에서는 간단하게 어떤 변화가 있었는지 개요를 설명드리고, 이 후의 글에서 하나씩 집어보려 합니다.


Standard C++ Library의 변경사항

  • STL의 많은 함수에서 move semantics와 perfect fowarding을 구현하기 위해서 rvalue reference를 사용하였습니다. Move semantics와 perfect fowarding은 변수나 매개변수의 메모리 할당이나 변수 할당의 성능을 크게 향상시킵니다.

  • auto_ptr 클래스보다 더 안전한 스마트 포인터형인 unique_ptr 클래스의 구현에도 Rvalue reference가 사용되었습니다. unique_ptr 클래스는 move는 할 수 있지만 copy는 불가능하며, safety에 영향을 미치지 않으면서 강한 소유 의미(strict ownership semantics)를 구현했습니다. 또한, unique_ptr 클래스는 rvalue references가 구현된 container들과 잘 동작합니다.
  • <memory>헤더에 포함된, 새로운 make_shared 함수는 어떤 객체가 만들어지는 동시에 shared pointer를 만들기에 편리하고, 안전하며, 효율적입니다.

  • 단반향으로 연결된 리스트(single linked list)가 <forward_list> 헤더에 의해서 지원됩니다.

  • cbegin, cend, crbegin, crend 멤버 함수는 container에서 앞으로 또는 뒤로 움직이는 const_iterator를 제공합니다.

  • <system_error> 헤더와 관련된 템플릿은 저수준 시스템 오류를 처리하는 것을 지원합니다.

  • exception_ptr 클래스의 멤버들은 쓰레드 간에 예외를 전송하기 위해 사용될 수 있습니다.

  • <codecvt> 헤더는 유니코드 문자의 다양한 인코딩을 다른 인코딩으로 변환하는 것을 지원합니다.

  • <allocators> 헤더는 node기반의 container에 대한 메모리 블럭의 할당과 해제를 돕는 여러 템플릿을 정의합니다.

  • <random> 헤더에 많은 업데이트가 있습니다.

다음 글들에서는 각각에 대해서 좀 더 자세히 소개하도록 하겠습니다.
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

Posted by 안준석 TedAhn
람다 함수 활용(2)

앞선 포스팅에서 람다 함수의 캡쳐(Capture) 기능에 대해 설명 했었습니다. (여기! 있습니다)
캡쳐 기능을 사용 하면 다음과 같이 활용 할 수 있습니다.

④ 템플릿을 대체하는 데 활용
람다 함수의 캡처 기능을 사용하면 람다 함수 몸체에서 외부 변수들을 마음껏 사용할 수 있을 뿐만 아니라 클로저(Closure)가 되어 함께 묶입니다. 이 기능을 활용하면 기존에 파라미터 타입과 개수 처리를 일반화하기 위해 사용하던 템플릿 사용을 지양할 수 있습니다. 아래 두 함수가 템플릿과 람다 함수를 이용해서 원하는 시점에 호출 되게 하려면 어떻게 하면 될까요?

// 템플릿 객체와 람다 함수를 이용해 간접적으로 호출될 예제 함수들


void Function_Arg1(int arg1)

{

cout << "Function_Arg1 : " << arg1 << endl;

}

 

void Function_Arg2(int arg1, const char* arg2)

{

cout << "Function_Arg2 : " << arg1 << "," << arg2 << endl;

}



위 두 함수를 특정 파라미터와 묶어 특정 시점에 호출해서 사용하고 싶을 경우 템플릿을 이용하면 아래와 같은 방법으로 사용할 수 있습니다.

// 템플릿 객체를 이용해 함수를 인자와 함께 묶어 특정 시점에 호출하는 예


int arg1 = 1004;

const char* arg2 = "Lambda!";

 

vector<ITask*> taskList;

 

// 1. 컨테이너에 담는다.

taskList.push_back( new Task_1<void, int>(&Function_Arg1, arg1) );

taskList.push_back( new Task_2<void, int, const char*>(&Function_Arg2, arg1, arg2) );

// 2. 특정 시점에 컨테이너를 순회하며 실행시킨다.

for( auto i = taskList.begin(); i != taskList.end(); ++i )

{

(*i)->Do();

}



위와 같은 코드로 템플릿 객체를 사용하려면 다음과 같은 코드가 필요합니다.

// 파라미터 1개짜리 함수를 담기 위한 템플릿 클래스


template<typename RetType, typename ArgType1>

class Task_1 : public ITask

{

typedef function<RetType(ArgType1)> FunctionType;

 

public:

Task_1(FunctionType f, ArgType1 a1)

: function_(f), arg1_(a1)

{

}

 

virtual void Do()

{

cout << "Task_1::Do()" << endl;

function_(arg1_);

}

private:

FunctionType   function_;

ArgType1       arg1_;

};

 

// 파라미터 2개짜리 함수를 담기 위한 템플릿 클래스

template<typename RetType, typename ArgType1, typename ArgType2>

class Task_2 : public ITask

{

typedef function<RetType(ArgType1,ArgType2)> FunctionType;

 

public:

Task_2(FunctionType f, ArgType1 a1, ArgType2 a2)

: function_(f), arg1_(a1), arg2_(a2)

{

}

 

virtual void Do()

{

cout << "Task_2::Do()" << endl;



실제 C++ 프로젝트를 진행하다 보면 위와 비슷한 형태의 템플릿 클래스를 자주 구현하게 됩니다. 이 때 발생하는 문제점은 함수의 파라미터가 늘어날 때마다 템플릿 클래스를 추가해 줘야 하고 코드가 직관적이지 않다는 점입니다. BOOST_PP를 이용하면 자동화할 수 있지만 디버깅이 굉장히 어렵고 작성자만 이해할 수 있는 코드가 만들어지곤 합니다. 때론 작성자도 이해 못하죠 ;)

그럼 이런 상황에서 람다 함수를 이용하면 어떨까요?
아래 예제를 보면 람다 함수의 캡처 기능을 이용해 깔끔하게 구현되는 것을 볼 수 있습니다. 뿐만 아니라 실행시킬 함수의 파라미터가 몇 개든 타입이 무엇이든 추가되는 코드는 없습니다. 앞으로 많은 부분에서 람다 함수를 이용해 템플릿 사용을 줄일 방안이 제안 되길 기대해 봅니다.


// 람다 함수를 이용해 함수와 인자를 묶어 특정 시점에 호출하는 예제


int arg1 = 1004;

const char* arg2 = "Lambda!"; 

...
...
typedef function<void(void)> LambdaType;


vector<LambdaType> lambdaList;

 

lambdaList.push_back( [=](){ Function_Arg1(arg1); } );

lambdaList.push_back( [=](){ Function_Arg2(arg1, arg2); } );

 

for_each( lambdaList.begin(), lambdaList.end(), [](LambdaType lambda)

{

lambda();

});




마치면서

4회에 걸쳐『Plus C++0x』람다(Lambda) 이야기를 했습니다. 막연히 람다(Lambda) 의 기능에 대해 설명 하기 보다는 이면에 깔린 배경 개념을 소개 함으로써 현대 프로그래밍 언어가 갖는 특징을 이야기 하고 싶었습니다.

다음 시리즈에서는 우측 값 참조(RValue Reference)에 대해 알아보고 C++0x에서 어떤 의미를 갖는지를 설명하려고 합니다. 람다(Lambda) 관련해서는 많은 내용을 한 번에 준비해서 쓰려니 고생스럽더군요. 이번엔 차근차근 포스팅 하면서 글을 완성할 수 있으면 좋겠네요 ;)

( 마이크로소프트웨어 6월호의 『생각의 직관적인 표현, 람다(Lambda)』를 보시면 보다 잘 정리 되고 추가 된 내용을 보실 수 있습니다. )


고자료
1. MSDN -
http://msdn.microsoft.com/en-us/library/dd293608.aspx
2. MSDN - http://msdn.microsoft.com/en-us/library/dd293599.aspx
3. MSDN - http://channel9.msdn.com/posts/kmcgrath/Lambda-Expressions-in-C/
4. MSDN - http://blogs.msdn.com/vcblog/archive/2008/11/18/stupid-lambda-tricks.aspx
5. Wikipedia - http://en.wikipedia.org/wiki/First-class_function
6. Wikipedia -  http://en.wikipedia.org/wiki/Higher-order_function
7. VSTS 2010 Team Blog -  http://vsts2010.net/category/Language%20Development/C++0x


저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

Posted by 안준석 TedAhn

람다 함수 활용(1)

지금까지 First-Class Object, Higher-Order Function, Closure와 같은 조금은 지루한 개념들을 살펴봤습니다. 이런 개념들을 설명하지 않으면 람다 함수를 함수 포인터나 함수 객체를 대체하는 Syntactic Sugar로 오해할 수도 있기 때문입니다. 또 어떻게 람다 함수를 일반 변수처럼 사용할 수 있는지 배경 지식을 설명하기 위해서였습니다.

그럼 이 람다 함수를 어디에 사용하면 좋을까요?앞으로 4가지 활용 예를 살펴 보겠습니다.


① 지연 호출(Deferred Call)에 활용
vector와 같은 자료구조에 저장한 후에 필요한 특정 시점에 호출하는 것입니다.
아래 예제에서는 람다 함수를 이용해 Task를 만들고 TaskManager라는 컨테이너 클래스를 통해 특정 시점에 람다 함수가 실행되는 것을 나타내고 있습니다.

TaskManager manager;

 

// TaskManager에 Task로 저장한다.

manager.AddTask( [](){ cout << “task1” << endl;} );

manager.AddTask( [](){ cout << “task2” << endl;} );

 

 

// 같은 스레드 혹은 다른 스레드에서 실행시킨다.

manager.Run();



② 비동기 호출과 결과 코드의 응집성을 높이는 데 활용

비동기 처리 코드의 문제점은 비동기 요청 함수를 호출하는 곳과 결과를 처리하는 함수가 동떨어져 있어 로직 흐름을 파악하기 어렵다는 것입니다. 비동기 요청 함수를 호출할 때 결과 처리에 대한 코드를 람다 함수로 구현해 파라미터로 전달하면 코드 응집성이 높아질 수 있습니다.
아래 예제에서는 Client 클래스의 요청에 대한 처리가 비동기적으로 이뤄질 때 람다 함수를 이용해 요청 시점에 결과를 어떻게 처리할 것인지 기술하는 것을 나타내고 있습니다.

 

class Client

{

public:

// 비동기 요청에 대한 결과를 처리할 람다 함수 타입

typedef function<void(int result)> AsynResultProcessor;

 

public:

void AsyncRequest(AsynResultEvent resultEvent)

{

resultEvent_ = resultEvent;

}

 

void Process()

{

Result_ = 1004;

 

// 미리 저장된 결과 처리 람다 함수를 호출한다.

resultProcessor_(result_);

}

private:

AsynResultProcessor    resultProcessor_;

int                      result_;

};

 

Client client;

 

// 응답을 어떻게 처리할 것인지 요청 시 기술할 수 있다.

client.AsyncRequest( [](int result)

{

// 어떻게 결과를 처리할 것인지 기술

cout << result << endl;

});

 

// 특정 시점에 처리한다.

client.Process();




③ 일회성 함수를 쉽게 구현하기 위해 활용

특히 STL을 이용하는 데 유용합니다. STL 알고리즘 함수들의 입력 파라미터로 람다 함수를 넘겨주면 따로 함수 객체를 정의하는 번거로움이 사라지고 코드 응집성이 높아지므로 STL 함수의 작동을 더 쉽게 이해할 수 있습니다. 아래 예제에서는 함수 객체와 람다 함수 이용을 비교해서 보여주고 있습니다.

 

//  for_each()를 위한 함수 객체

//  일회성 호출을 위해 많은 코드가 필요하고 가독성도 떨어진다.

struct LambdaFunctor

{

void operator()(int n) const

{

cout << n << " ";

}

};

 

int main()

{

vector<int> v;

 

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

{

v.push_back(i);

}

 

// 1. 함수 객체를 이용한 코드

for_each(v.begin(), v.end(), LambdaFunctor());

 

// 2. 람다 함수를 이용한 코드

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

 

cout << endl;

}




포스팅이 길어졌네요~
다음 글에서 람다 활용에 대해 마저 이야기 하고 "람다 이야기" 시리즈를 마치도록 하겠습니다.


저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

Posted by 안준석 TedAhn

지난 글에서 이야기 했던 람다(Lambda)의 배경 개념에 대해 알아보겠습니다.

람다 함수는 First-Class Object?
프로그래밍 언어를 이루는 class, struct, int와 같은 개체들 중에서 아래 조건을 만족하면 First-Class Object로 분류합니다. (First-Class Object는 분류의 한 갈래를 의미하는 것이지 상하 관계를 의미하진 않습니다.)

- 변수와 자료구조에 저장하고 사용할 수 있다.
- 함수의 입력 값으로 사용할 수 있다.
- 함수의 반환 값으로 사용할 수 있다.
- 실행 시간에 생성할 수 있다.

즉, 기존 C++에서는 class, struct, int 등이 First-Class Object 였고  아래 예제에서 볼 수 있듯이 C++0x에서 람다 함수(Lambda Function)가 First-Class Object의 조건들을 만족시킵니다.

...
int main()

{

string text = "C++0x Lambda!";

 

// 1. "코드 조각" 변수에 대입하기

function<void()> lambda = [=]()

{                               

cout << text << endl;

};

 

// 2. "코드 조각"을 자료구조에 저장하기

vector< function<void()> > container;

{               

container.push_back( lambda );

container.push_back( [=](){ cout << text << endl; } );

};

 

// 3. "코드 조각"을 함수의 입력 파라미터로 사용하기

for_each( container.begin(), container.end(), [](const function<void()>& f){ f(); } );

 

return 0;

}


위 조건들을 통해 First-Class Object들은 프로그래밍 언어에서 별 다른 제약 조건 없이 변수로 사용할 수 있게 됩니다. 최근 여러 언어들이 함수를 First-Class Object로 제공하는 이유도 함수를 값 처럼 사용할 수 있게 되면 여러 모로 유용하기 때문입니다. 특히 함수는 다른 First-Class Object들을 생성하거나 조작하는 일련의 코드 묶음이기 때문입니다.

C++0x에서는 람다 함수(Lambda Function)를 First-Class Object로 제공함으로써 코드 조각을 일반 변수처럼 사용할 수 있게 해줍니다.



람다 함수는 Higher-Order Function?

함수의 입력 값으로 함수를 전달 받거나 함수의 결과 값으로 함수를 반환할 수 있을 때 Higher-Order Function이라고 합니다. 아래 예제 코드를 보면 쉽게 알 수 있습니다.

 

int main()

{

// 1. 람다 함수를 반환 값으로 한다.

auto g = [](int x) -> function<int (int)>

{          

return [=](int y) { return x + y; };

};

 

// 2. 람다 함수를 입력 값으로 받는다.

auto h = [](const function<int (int)>& f, int z)

{

return f(z) + 1;

};

 

auto a = h( g(7), 8 );

 

cout << a << endl;

}



예제를 보면 h( g(7), 8 )과 같이 표현된 것을 볼 수 있습니다. 마치 수학 책에서 보던 h( g(x), y )처럼 표현할 수 있는 것입니다. 람다 함수는 C++0x에서 First-Class Object 조건을 만족시키기 때문에 Higher-Order Function의 요구사항 또한 만족시킵니다.


람다 함수는 Closure?
클로저(Closure)는 함수를 호출한 상위 코드 블록의 변수들이 호출된 함수와 묶인 것을 뜻합니다. 즉 호출된 함수는 상위 코드 블록의 외부 변수와 묶여 자기만의 상태를 갖게 되는 것입니다.
C++0x에서는 람다 함수(Lambda Function)가 상위 코드 블록의 변수들과 묶여 클로저(Closure)가 될 수 있다. 이때 람다 함수에서 외부 변수들을 참조하는 것을 ‘캡처’라고 말합니다.
캡처(Capture)는 두 가지 방법으로 할 수 있습니다. 람다 시작을 나타내는 [] 기호 사이에 =과 & 기호를 이용할 수 있습니다. [=]은 값 복사를 의미하고 [&]는 값 참조를 의미합니다. 아래 예제 코드를 보시죠.

 

void capture()

{

int a = 0;

int b = 1;

int c = 2;

 

// 1. default 값 복사 캡처. 상위 코드 블록의 지역 변수 모두 값 복사 가능

[=](){ cout << a << “ “ << b << endl; }();  // 0 1 출력

 

// 2. default 값 참조 캡처. 상위 코드 블록의 지역 변수 모두 값 참조 가능

[&](){ cout << a << “ “ << b++ << endl;}(); // 0 1 출력

 

// 3. default 값 복사 캡처, b와 c 참조 캡처

[=, &b, &c](){ cout << a << “ “ << b << “ ” << c << endl; }();  // 0 2 2 출력

}



이번 포스팅에선 몇 가지 배경 개념들을 알아봤는데요, 도움이 되셨는지 모르겠습니다 ;)
다음 글에선 람다 함수의 활용 방안에 대해 알아보겠습니다.

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

  1. 좋은 설명 감사합니다.^^ 내용 중에 default 값의 정의가 정확히 어떤 것인지 알고 싶습니다.

Posted by 안준석 TedAhn

마이크로소프트웨어 6월호에 실릴 C++ 0x 관련 글을 썼습니다.
제목은 『생각의 직관적인 표현, 람다(Lambda)』입니다.  팀 블로그에서는 『Plus C++0x』라는 제목으로 포스팅 하려고 합니다. 기존에 흥배님께서 C++ 0x에 대해 이미 좋은 글들을 많이 쓰셔서 표절하지 않으면서 무엇을 써야 할지 정말 많은 고민을 했답니다. ;)

그래서 약간 Advanced 하면서 불친절하게(?) 특정 주제에 대해 나름대로 재해석해보기로 했습니다. 첫 주제는 람다(Lambda) 로 잡았구요 다음은 우측값 참조(RValue Reference) 에 대해 쓰려고 합니다. 전체적인 아웃라인은 여기를 보시면 됩니다.


C++0x에 함수형 프로그래밍 언어 기능 추가!?
C++를 사용하다가 요즘 인기 있는 Python, C#, Ruby 같은 언어들을 쓰게 되면 무엇보다 직관적이고 편하다는 생각이 듭니다. 이 언어들은 람다(Lambda)와 클로저(Closure) 같은 함수형 프로그래밍 언어의 기능들을 지원함으로써 생각을 보다 직관적이고 효율적인 코드로 표현할 수 있게 합니다.


그렇다면 C++는 어떨까요?
포인터와 함수 객체 그리고 템플릿으로 점철된 C++ 프로그래밍 세계에도 이런 함수형 프로그래밍 언어의 기능들이 추가되면 직관적이고 덜 수고스러운 코딩을 할 수 있을까요?
마침 비주얼 스튜디오 2010이 발표되면서 비주얼 C++ 10가 공개됐습니다. 인텔리센스 기능 향상, 실시간 에러 검사, 병렬 라이브러리와 같은 막강한 기능이 추가됐지만 무엇보다도 C++0x라는 C++의 새로운 표준을 충실히 구현함으로써 C++ 프로그래머의 생산성을 높여 주목을 끌고 있습니다.
특히 C++0x는 언어의 핵심 기능으로 앞서 언급했던 람다(Lambda)와 클로저(Closure)를 기술하고 있고 이를 충실히 구현한 비주얼 C++ 10을 통해 보다 쉽게 사용할 수 있습니다.

이번 『Plus C++0x 람다이야기』를 통해 전달할 핵심 내용은 다음 두 가지 입니다.

C++0x에서 코드 조각(a piece of code)을 일반 변수처럼 사용할 수 있게 됐다.
C++0x에서 클로저(Closure)를 사용할 수 있게 됐다.


아래 코드를 보시죠.

#include <iostream>

#include <vector>

#include <string>

#include <algorithm>

#include <functional>

 

using namespace std;

 

int main()

{

string text = "C++0x Lambda!";

 

// 1. "코드 조각" 변수에 대입하기

function<void()> lambda = [=]()

{                               

cout << text << endl;

};

 

// 2. "코드 조각"을 자료구조에 저장하기

vector< function<void()> > container;

{               

container.push_back( lambda );

container.push_back( [=](){ cout << text << endl; } );

};

 

// 3. "코드 조각"을 함수의 입력 파라미터로 사용하기

for_each( container.begin(), container.end(), [](const function<void()>& f){ f(); } );

 

return 0;

}



위 예제에서 보는 것처럼 이렇게 일반 변수처럼 대입할 수 있고, 자료구조에 저장할 수 있고 함수의 입출력 값으로 사용할 수 있는 코드 조각을 람다(Lambda) 혹은 람다 함수(Lambda Function)라고 합니다.

람다(Lambda)를 이해하고 어떻게 사용하며 활용할지 설명하기 위해 First-Class Object, Higher-Order Function, Closure 같은 프로그래밍 언어 수업 시간에 나올 법한 개념들을 소개하려고 합니다. 개념적인 이해를 통해 람다(Lambda)와 친해져 보시죠 ;)

또 어떻게 사용 할지 몇 가지 사용 패턴을 제안해 볼까 합니다.
(약간 불친절하게 글이 진행됩니다. http://vsts2010.net에 있는C++0x 관련 글들을 먼저 읽어 볼 것을 권장합니다.)


람다 함수는 함수 안에 정의할 수 있는 이름 없는 함수(anonymous function)? 

...

void outer_function()

{

void inner_function()

{

}

}


위 코드를 보면 outer_function() 함수 안에 inner_function() 함수가 정의되어 있습니다. C++에서 이런 문법 사용이 가능할까요? 물론 사용할 수 없습니다. 즉, 함수 안에서 함수를 선언하고 정의할 수 없습니다. 그러나 람다 함수(Lambda Function)는 함수 안에서 선언하고 정의하고 호출할 수 있습니다. 


void outer_function()

{

// 이름 없는 함수! 람다!

[](){}();

}


위 예제에서 아무 동작을 하지 않는 람다 함수가 선언, 정의, 호출 됐습니다.

다음 편에서 이런 동작을 가능하게 하는 개념적 배경인 First-Class Object, Higher-Order Function, Closure 에 대해 알아보도록 하겠습니다.

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

Posted by 흥배

C++0x 관련 책 "Visual C++ 10 C++0x"가 오늘 한국 MSDN 사이트에 올라왔습니다.

e-book으로 보기를 원하는 분이나 책을 얻지 못한 분들은 다운로드 해서 보세요

 

MSDN : http://msdn.microsoft.com/ko-kr/default.aspx


 


Visual Studio의 시작 페이지에도 다운로드 링크가 표시됩니다.

 

 

그리고 책에 오타가 있습니다.

48페이지 decltype 설명에서 오타가 있습니다.




팀블로그에 예전에 RValue Reference lambda 관련 글을 올린 적이 있는데 책을 만들 때 제가 올린 글을 다시 보니 설명에 미흡한 부분이 많아서 RValue Reference lambda는 새로 적었습니다. 그러니 RValue Reference lambda를 공부할 때는 꼭 블로그에 올라와 있는 글보다 이 책의 글을 보시기 바랍니다.


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

댓글을 달아 주세요

  1. 와 visual studio 시작페이지에 뜨는거 너무 멋있습니다 +_+)b
    글 정리하신다고 정말 수고 많으셨어요 ^^

  2. 이상영 2010/04/21 00:05

    수고 하셨습니다. 좋은 글 감사합니다.

  3. 안승근 2010/04/22 11:33

    visual studio 시작페이지에서도 뜨고 데브피아에도 누군가 링크를 걸어주셨더라고요.
    잘받아서 보았습니다. ^^

  4. 꽃집총간, 이상영, 안승근 // 고맙습니다.^^

Posted by 흥배

4월 15일에 열린 “C++ 개발자/게임개발자를 위한 VS2010 세미나에서 제가 이때까지 VSTS 2010 팀블로그에 올린 C++0x와 관련된 글을 작은 책으로 만든 것이 마지막에 참석하신 분들에게 배포 되었습니다.

 

원래는 pdf 파일로만 공개될 줄 알았는데 작은 분량이지만 이렇게 책으로 나왔습니다. 생각 이상으로 책 편집도 잘 되었더군요. 책 내용은 팀블로그에 올린 글을 좀 더 다듬었습니다만 RValue Reference lambda는 제가 올린 글이 좋지 못해서 새롭게 적었습니다.

 

제가 글을 아직은 잘 적는 편은 아니라서 글이 매끄럽지 못할 수도 있습니다만 좋게 봐 주시기를 바랍니다.^^;

이 책에 나온 것만 아시면 VC++ 10에 추가된 C++0x의 기능을 사용하기에는 전혀 문제가 없다고 생각합니다. 꼭 도움이 되었으면 합니다.


이번 세미나를 위해 한국에 오신 VC++ 제품의 PM인 Ulzii씨의 싸인도 받았습니다.^^

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

댓글을 달아 주세요

  1. Simple Coder 2010/04/17 09:14

    PDF 파일 좀 올려주실 수 있으신지요?

  2. 얇지만 내용이 알차서 보기도 편하네요.. 잘 보겠습니다.^^

  3. 이거 PDF파일 받을 수 있나요?

  4. decltype 소개에 decltype(HP*) pHp = &hp;

    이 문법이 오타가 났네요 ^^;

    decltype(HP) *pHp = &hp;

  5. 호환성 2010/04/18 19:10

    하나 궁금한게있습니다.
    VS 2010 에 C++ 0x가 추가되었는데 기존의 컴파일러 VS 2003 ~ 2008 에서는 C++ 0x를 사용하지 못하게 되는 건가요??
    VS 2003이후 컴파일에 문제가 없으면 2010에서도 문제가 없다고 했었는데 그 반대는 어떤지 궁금하네요.
    마이크로소프트에서 따로 업데이트를 지원해 주는건지요 :)

    • 앞으로도 2003 ~ 2008은 C++0x를 지원하지 않습니다. 다만 2008에 있는 tr1(SP1 설치 후 사용 가능)은 C++0x의 라이브러리 부분입니다.

  6. Synch3D // 고맙습니다.^^
    Simple Coder, gerndal // 아마 조만간 한국 MSDN 홈페이지에 공개될 예정으로 알고 있습니다.

  7. decltype에 오타가 있습니다. ^^;;;;

    decltype(Hp*) pHp = &Hp; 이 아니고
    decltype(Hp) *pHp = &Hp; 입니다.

Posted by 흥배

Visual C++ 팀 블로그에 C++0x Core Language Features In VC10: The Table라는 이름으로 C++0x의 기능 중 코어 언어와 관련된 것 중에서 VC++ 10에 구현된 것들을 테이블 표로 정리되어 있습니다.

GCC C++0x 구현 항목 테이블 표 형식을 차용했다고 하네요.


 

 

위의 테이블 표에서는 C++0x가 처음 구현된 VC++9VC++ 10을 비교하고 있습니다.

 

그리고 글의 마지막에 작년에 Boost Con(Boost 라이브러리 관련 행사)에서 발표한 자료가 첨부 파일로 있습니다. 이 문서를 보면 VC++ 10에서 구현한 C++0x의 코어 언어 기능들을 설명하고 있습니다.

 

문서를 보니 큰 기능들은 제가 작년부터 공부하면서 저희 팀 블로그나 여러 장소에서 설명 하였지만 일부 기능은 저도 미쳐 파악 하지 못한 것들도 있더군요. 앞으로 이런 빠진 부분에 대해서 팀 블로그를 통해서 설명해 드리겠습니다.^^


 

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

댓글을 달아 주세요

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' 카테고리의 다른 글

C++0x 관련 책 "Visual C++ 10과 C++0x"  (9) 2010/04/17
VC++ 10에 구현된 C++0x의 코어 언어 기능들  (0) 2010/04/12
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

댓글을 달아 주세요

Posted by 흥배

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' 카테고리의 다른 글

VC++ 10에 구현된 C++0x의 코어 언어 기능들  (0) 2010/04/12
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

댓글을 달아 주세요

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

댓글을 달아 주세요

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

댓글을 달아 주세요

  1. 라도파 2009/08/24 07:27

    아주 재미있었습니다...^^

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

댓글을 달아 주세요

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

댓글을 달아 주세요

Posted by 흥배

이번은 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

댓글을 달아 주세요

  1. 최근 channel9 비디오를 보니 이 람다를 boost에서처럼 라이브러리 기능으로 가져갈지 언어 자체 기능으로 가져갈지 논란이 많았다더군요. 끝내 언어 자체 기능으로 가져간 것을 보면 람다를 상당히 필수적인 기능으로 본듯합니다.

    • 그렇군요. 근래에 VC++ 팀에서 Boost 컨퍼런스에서 람다로만 학 세션을 강연했더군요. 람다 이거 파봐야 될게 많을것 같아요

  2. 그렇군요. 근래에 VC++ 팀에서 Boost 컨퍼런스에서 람다로만 학 세션을 강연했더군요. 람다 이거 파봐야 될게 많을것 같아요

  3. 와우... 2010/01/27 06:12

    골수 C++ 추종자로써.. C++이 계속 발전 한다는 것 (특히 람다같은 것의 지원)은 정말 환영할 일입니다...

Posted by 흥배

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

댓글을 달아 주세요

  1. template을 지우고 T를 int로 쓰면 에러가 나네요... 꼭 템플릿을 써줘야 하는 이유가 있나요?

Posted by 흥배

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

댓글을 달아 주세요

  1. 잘보았습니다. ^^

Posted by 흥배

이번 회는 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

댓글을 달아 주세요

Posted by 흥배

이전 회에서 이야기 했던 것을 토대로 이번에는 코드로 그것을 보여드리도록 하겠습니다. 이전 글에서 알쏭달쏭 했던 분들은 앞으로 보여줄 코드를 보면 앞의 설명에 대해서 이해할 수 있을 것입니다.

 첫 번째 글이 지루하고 애매했다면 두 번째 글부터 아주 흥미롭고 중요한 이야기가 나옵니다.

 

 

 

Move 생성자와 Move 대입 연산자

 

기존의 class에서는 복사 생성자와 대입 연산자만 정의할 수 있었지만 RValue Reference에 의해 ‘Move 생성자‘Move 대입 연산자라는 것이 생겼습니다.

 

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

    }

 

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

};

 

C++0x 이전에는 없던 것이 Move 생성자 QuestInfo(QuestInfo&& quest) Move 대입 연산자 QuestInfo& operator=(QuestInfo&& quest)입니다.

 

외견 상으로 기존의 복사 생성자, 대입 연산자와의 차이는 함수 파라메터에서 ‘&’가 아닌 ‘&&’을 사용하는 것입니다.

 

 

 

성능이 좋아진다고 했는데….

 

RValue Reference를 설명할 때 이것 덕분에 프로그램의 성능이 좋아진다고 했습니다.

왜 그럴까요? <Code 1>에서 복사 생성자와 Move 생성자, 대입 연산자와 Move 대입 연산자의 구현을 잘 보세요. 다르지 않나요?

 

RValue Reference Move Semantics에 의해 복사가 아닌 메모리 이동이라는 것을 할 수 있습니다. <Code 1> Move 생성자와 Move 대입 연산자는 넘겨 받은 인자를 복사하지 않고 메모리 상의 이동을 하고 있습니다.

 

< Code 2. 복사 생성자와 Move 생성자 >

// 복사 생성자

QuestInfo(const QuestInfo& quest)

        : Name(new char[quest.NameLen]), NameLen(quest.NameLen)

{

      memcpy(Name, quest.Name, quest.NameLen);

}

 

// Move 생성자

// Move 생성자

    QuestInfo(QuestInfo&& quest)

        : Name(quest.Name), NameLen(quest.NameLen)

{

      quest.Name = NULL;

      quest.NameLen = 0;

}

 

복사 생성자는 new로 메모리 할당을 한 후 메모리 복사를 하지만, Move 생성자는 메모리 이동을 하고 있습니다.

 

크기가 작은 오브젝트에서는 큰 의미가 없지만 크기가 큰 오브젝트에서는 그 차이가 무시할 수 없을 것입니다.

 

 

 

RValue Reference LValue Reference는 언제 사용할까요?

 

RValue Reference LValue Reference(기존의 참조)는 언제 사용하는지 구분 할 수 있으시나요?

RValue Reference가 생김으로 참조에 관한 룰이 좀 더 추가 되었습니다. 또한 이 두 개의 참조에 대해서 정확하게 알기 위해서는 참조에 대해서 좀 더 세세하게 알아야 됩니다.

그래서 어떤 사람들은 RValue Reference가 좋기는 하지만 이것 때문에 가뜩이나 복잡한 C++이 더 복잡하게 되었다고 불평하기도 합니다.

 

글이 좀 길어져서 RValue Reference가 사용되는 경우에 대한 것은 다음 글에서 코드로 보여드리겠습니다.

 

 

ps :

RValue Reference에 대해서 상세하게 알고 싶은 분들은 Microsoft VC++ 팀 블로그에 포스팅 된 글을 보시기를 바랍니다(영어이고, 내용도 길고, 좀 복잡해서 한번에 팍 하고 이해는 안될 수 있습니다).

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

혹시 위 글을 꼭 알고 싶은데 영어 때문에 난감하고, 시간이 부족하니 정리해 주기를 바라는 분이 있다면 댓글로 알려주시면 뒤에 제가 이 글을 정리해서 올려보겠습니다.

 

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

댓글을 달아 주세요

  1. 정의의소 2009/05/06 18:15

    재밌게 잘 봤습니다. 코딩 안 한지가 오래되어서 가물가물 거리네요.. ㅡㅡ;

    • 부족한 글인데 좋게 봐주셨어 고맙습니다.^^
      코딩은 손 놓으면 감 잠는데 시간이 살짝 걸리죠