'2015/07'에 해당되는 글 2건

  1. 2015.07.17 람다 (2)
  2. 2015.07.16 명함
2015.07.17 11:23

람다함수

 


목차


람다함수..?

문법

그래서 이걸 왜 쓸까?

결론

 

 


람다함수..?

 

람다함수는 무엇일까?
람다함수는 쉽게 인식하자면, 이름이 없는 함수 라고 생각하면 된다.

 



고전적으로 우리가 생각하는 함수는

1 클래스를 만들고 해더에 선언한다
2 cpp에서 구현한다.
3.함수를 호출한다.

혹은

1.해더에서 static으로 선언과 구현을 동시에 한다.
2.전역으로 사용할 수 있는 함수를 호출한다.

의 구조인데
둘 중 무엇이 되든 함수는 이름이 있기 마련이지만,
람다함수는 이름이 없다. 

 



[](){}

이것이 람다함수의 기본형
처음 보면 정말 해괴하고 기괴하게 생겼다.



기존의 함수

void fnc(int nData);
클래스에 선언하고

void 클래스명::fnc(int nData)
{
printf("nData = %d", nData);
}
구현해서

fnc(10);
호출




람다 함수

auto fnc = [](int nData){printf("nData = %d", nData);};
fnc는 람다함수가 된다.

*더 정확히 따지자면, fnc는 std::function<void(int)> 가 됨

fnc(10);
호출

 

 

 

그래서 람다함수가 뭔데? 

 

람다함수는 std::function을 즉석해서 구현 가능한 하나의 문법이라고 생각하면 이해가 빠르다.

그런데 std::function 자체를 모른다면, 람다함수의 이해나 사용은 포기하는 것이 좋다.  

 

 

 

 

 

문법

 

 

기본형 : [캡쳐](파라미터)->리턴형{구현할 내용} 

 

[](){}

의미 없는 람다함수이지만 최소한의 문법으로 구현된 람다함수 

 

[=](int nData)->int{

    return nData; 

람다함수 외부에 있는 변수를 람다함수 내에서 이용가능하면서,

파라미터는 int를 받고, 리턴형은 int이며 입력된 파라미터 값을 그대로 리턴하는 내용의 람다함수 

 

 

 

 

 

 

[] 캡쳐 : 람다 함수 외부에 있는 변수를 람다함수 내에서 사용하고 싶을때 그 통로 역할을 한다.

 

예)

 

int nDataA = 10;

int nDataB = 10;

 

 

auto fncLamda = [nDataA](){ //람다 함수 내에서 nDataA라는 변수를 사용하고 싶어요. 

 

printf(nDataA); //람다함수 외부에 있는 nData를 람다 함수 내에서 사용했다. 

printf(nDataB); //에러 

 

};

 

 

auto fncLamdaFail = [](){ //난 외부 변수따윈 쓰지 않겠어 

 

printf(nDataA); //에러 

printf(nDataB); //에러 

 

};

 

 

auto fncLamdaAll = [=](){ //외부에 있는 모든 변수를 밸류로 사용하고 싶어요. 

 

printf(nDataA); //가능 

printf(nDataB); //가능

 

};

 

auto fncLamda = [nDataA, nDataB](){ //람다 함수 내에서 nDataA와 nDataB라는 두개의 변수를 사용하고 싶어요. 

 

printf(nDataA); //가능

printf(nDataB); //가능

 

};

 

[] 캡쳐 안에는 외부에서 사용하고 싶은 변수명을 사용하거나

= 로 모두다 사용하거나

& 로 레퍼런스를 얻어올 수 있다.

&변수명 도 가능하다. 

 

기본적으로 캡쳐의 = 는 const 성질이 있어서 외부 변수를 람다함수 내부에서는 읽기만 가능하고 바꾸진 못하지만,

언제나 그렇듯 const가 싫으면 mutable을 람다에 걸면 그만이다.

 

예) [=]()mutable{nData = 222;} 

 

 

=, &의 성능 차이에 대해서는 글쓴이가 아는 것이 없어, 설명하지 못한다. 

 

 

 

() 파라미터 : 함수의 파라미터와 동일하므로, 설명을 생략하겠다. 

파라미터의 사용은 분명히 캡쳐와는 구분을 지어야 한다.

 

 

 

 

->리턴형 :  앞에 -> 참조연산자를 사용한다.

 

{} 함수의 내용: 이것도 기존 함수와 똑같다. 람다함수가 무슨 동작을 하는지 구현한다. 

람다함수에 리턴형이 있다면 반드시 리턴을 해야하는 성질도 똑같다.

 

주의할 점은, 람다함수는 주로 함수의 파라미터로 사용되는 경우가 많아서 그 끝을 알아보기가 매우 힘들다는 것이다.

중괄호의 양 끝을 잘 관리해야 한다. 

 

 

 

 

실전 예제

 

CCTextureCache.cpp 

void TextureCache::unbindImageAsync(const std::string& filename)

{

...

auto found = std::find_if(_imageInfoQueue->begin(), _imageInfoQueue->end(), [&fullpath](ImageInfo* ptr)->bool{ return ptr->asyncStruct->filename == fullpath; }); 

... 

}

 

람다함수 하나가 find_if의 파라미터로 껴있다.

 

[&fullpath](ImageInfo* ptr)->bool{ return ptr->asyncStruct->filename == fullpath; }

외부에 있는 fullpath의 주소를 람다 내부에서 사용하며

ImageInfo의 포인터를 파라미터로 받으며

bool을 리턴하고

return ptr->asyncStruct->filename == fullpath; 라는 내용의 람다함수 

 

 

 

 

그래서 이걸 왜 쓸까?

 

앞서 설명한 간단한 람다함수 예제만 보면 느끼는 생각은,  

아니 왜 그냥 함수 만들어서 사용하지 굳이 이걸 알아보기도 어려운 람다함수로 만들어서 사용하는거지? 이다. 

 

 

 

1.함수의 파라미터에 함수를 넣고 싶은 경우

 

람다함수가 필요한 상황은, 함수의 파라미터에 함수를 넣고 싶을때가 많다.

그러나 기존의 함수 선언, 구현, 호출 방식으로는 이런 것이 상당히 귀찮다.

 

 

함수 파라미터에 std::function이 사용되는 예 

 

선언 

void makePopup(Node* pPopup, std::function<void(int, std::string)> fncConfirm, std::function<void(int, std::string)> fncCancel);

 

구현 

void makePopup(Node* pPopup, std::function<void(int, std::string)> fncConfirm, std::function<void(int, std::string)> fncCancel)
{

..... 

    fncConfirm(10, "ok pu");
    
    fncCancel(10, " 취소를 눌렀어..");

    printf("ok");
}

 

예를 들어 팝업을 만드는 함수가 있다고 하자.

이 함수는 팝업의 노드를 필요로하고

팝업에서 확인을 눌렀을 때의 동작과

팝업에서 취소를 눌렀을 때의 동작을 입력해야 한다.

 

그렇다면, 기존의 방식이라면  

class C_CLASS

void confirmPopup1(int nData, const std::string& strMessage);
void cancelPopup1(int nData, const std::string& strMessage);

... 

}; 

를 해더에 선언 

 


void C_CLASS::confirmPopup1(int nData, const std::string& strMessage)
{

   m_nDataConfirm = nData; 

    printf("%s \n", strMessage.c_str());
}


void C_CLASS::cancelPopup1(int nData, const std::string& strMessage)
{

    m_nDataCancel = nData; 

    printf("%s \n", strMessage.c_str());
}

선언한 함수를 구현

 

 

makePopup(pPopup, confirmPopup1, cancelPopup1);

팝업 호출 

 

 

 

 

 

그러나 람다함수가 등장하면 

 

팝업을 만드는 함수 호출 

makePopup(pPopup, [=](int nData, std::string strMessage){
        ...
        m_nDataConfirm = nData;
        printf("%s \n", strMessage.c_str());
    
    },[=](int nData, std::string strMessage){

       m_nDataCancel = nData;
        printf("%s .....

    }); 

 

이렇게 한번에 구현이 가능하다. 

 

 

 

2.함수를 컨테이너에 넣어 보관하는 경우 

 

if문을 사용하지 않고 배열, std::map등에 함수들을 미리 만들어 놓고 꺼내 쓸 때는 함수 포인터가 필연적인데,

함수포인터는 클래스에서 멤버로 가지고 있으려면 너무 과정이 복잡하다. 


클래스 멤버 함수 포인터 : http://gamejung13.blog.me/220217894884?Redirect=Log&from=postView

 

미리 함수를 간단하게 만들어서 필요할때 빠르게 꺼내 사용할때도 람다함수는 꽤나 유용하다. 

 

 

std::map<std::string, std::function<bool(int nData)> > mapData;

 

//맵 데이터에 대괄호 연산자로 입력

mapData["test"] = [=](int nData){  //"test"라는 키값으로 함수포인터가 가르키는 함수 내용을 구현해버린다.

//pair로 설명하자면, first에 키값인 "test"가

//second에는 람다함수가 들어간다.

 

printf("%d\n", nData);

return true;

 }

 

//호출 

mapData["test"](10);

 

이런식으로 함수들을 맵에 간단하게 미리 만들어 놓고 키값으로 꺼내 사용할때도 유용하다.

이를 언제 하나하나 다 선언하고 구현해서 맵에 입력할 것인가? 

 

 

 

 

 

결론 

 

람다함수는 코코스에서 간단한 콜백함수를 즉석에서 구현할때 꽤 유용하다.

클래스 멤버에 함수를 선언하고 cpp에서 구현하고 호출뒤에 이상하게 동떨어진 곳에 선언된 함수를 찾아갈 필요 없이 그자리에서 콜백함수를 만들 수 있다. 지원하는 인터페이스를 살펴보면, 대놓고 파라미터 설명에 람다함수를 집어넣으라는 인터페이스가 종종 보인다.

 

특히 함수들을 미리 컨테이너에 보관해놓고 키값으로 호출하고 싶을때, 그 입력 방법이 매우 간결해진다.

std::map의 대괄호 연산자를 조심히 이용하면, 제법 괜찮은 방법이다. 

 

그러나 람다함수안에 구현해야하는 내용이 많아진다면, 차라리 함수로 빼는 것도 좋은 방법이다.

람다안에 람다 람다안에 람다 람다안에 람다 이런식으로 코딩을 하다간, 괄호의 끝 찾기가 매우 곤란하기 때문이다.

 

생긴것이 매우 해괴하게 생기고 코드가 많이 지저분해지는데, 문법을 전혀 모르면 곤란한 일이 많다. 


출처 : http://cafe.naver.com/cocos2dxusers/25782

신고
Posted by 우엉 여왕님!! ghostkyow
2015.07.16 16:21



신고
Posted by 우엉 여왕님!! ghostkyow

티스토리 툴바