프로그래밍/C++

[C++] 람다식

FORHAPPy 2021. 12. 22. 08:40

C++람다식 사용이유와 사용 방법

 

 

기본문법

[] () -> int {}

 

[] : 캡쳐블록 ( 사용시 외부변수를 캡쳐해 람다의 몸통에서 사용가능하다.)

() :  전달인자 

-> 반환타입

{} : 함수 몸통

 

여기서 캡쳐블록 [] 와 전달인자. ()와 리턴 타입{}을 생략할 수 있다. 

 

 

사용 이유 

함수객체와 다르게 클래스 선언을 할 필요가 없다. 

함수포인터와는 다르게 인라인화가 가능하다.

 


람다의 캡쳐부분

 

기본적으로 클래스의 멤버 변수를 람다의 바디 안에서 사용했다면, 이는 this->가 생략되어 있다.

 

& :  모든 외부변수 참조 

&, initialize : 모든 외부변수 참조 및 초기화 가능

 

= : 모든 외부변수 복사 (

=, intiailize : 모든 외부변수 복사 및 초기화 가능

 

[localVar] : 특정한 변수를 값에 의한 캡쳐 -> 변경해도 람다식 밖에서 그 변경이 적용되지 않는다. 

 [&localVar] : 특정한 변수를 레퍼런스에 의한 캡쳐 -> 변경하면 람다식 밖에서도 그대로 적용된다. 

 

 

 


capture로 =를 가져왔을 경우  const 로 읽기만 가능하다.

안에서 값변경 불가능 하다.

 

    int x = 10;
    double y = 5.4;
    string s = "hwan";


    auto l1 = [=](){
        int cp_x = x;
        double cp_y = y;
        string cp_s = s;
        return x + y;
    }();

    auto l2 = [=, x = x + 1](){
        cout << "l2함수의 x의 주소 값 : " << &x << endl;
        return x * x;
    };


    cout << "l1 = " << l1 << endl;
    
    cout << "l2 = " << l2() << endl;
    cout << "l2 = " << l2() << endl;
    cout << "x = " << x << endl;
    cout << "main함수의 x 주소 값 : " << &x << endl;

15.4, 121, 121, 10(x값은 그대로다.)  l2의 x랑 외부변수 x랑은 다른 값이다. (같은이름이면 외부변수는 사용되지 않는다.)

따라서 캡쳐안에서 연산을 할 수 있는 이유이다. 


람다의 특징 

 

1. 람다에서 선언된 변수는 프로그램이 끝날떄 까지 변수가 사라지지 않는다. 

2. 외부변수와 같은 변수 이름으로 초기화를 한다면 해당 외부변수는 사용되지 않는다. 

3. capture 블록에서 초기화된 변수는 const 로 선언이 된다. 초기화된 변수는 변경이 불가능하다.

4. capture로 =를 가져왔을 경우  const 로 읽기만 가능하다.

5. 람다는 이름은 없지만 고유한 객체이다. ( 검파일과정에서 람다라는 객체를 생성한다.)

6. 인라인화가 가능하다는게 람다의 가장큰 장점이다. 

 

 

 


 

#include <iostream>

using namespace std;

int main() {
    int x = 10;

    auto l1 = [&](){
        x = 5;
        return x;
    };

    auto l2 = [&, x = x + 100](){
        return x;   
    };

    cout << l1() << endl;
    cout << "main  x  : " << x << endl;;
    cout << l2() << endl;
    cout << "main  x  : " << x << endl;;

    return 0;
}

결과는 5, 5, 110, 5 가 나온다. 

이유는 capture안의 &의 특성이다.

 

ㅣ1에서 [&]이기 때문에 안에서 x의 값이 5로 바뀌었다 .

따라서 후에 출력되는 x는 5값을 가진다. 

 

l2에서 capture안의 x = x + 100 의x는 외부변수 x랑은 다른값이다. 

따라서 변하지 않는다. 

 

그래서 l2를 빠져나와도 x값은 그대로 5가 된다.


그런데 여기서 'x = x + 100' 는 105가 되어야 할거같은데 110이 된다. 

 

왜그럴까?

 

람다는 기본적으로 cosnt 로 변수값을 읽어오는 곳이 있다. 

바로 코드블럭 맨뒤의 소괄후 () 부분이다. 

 

ㅣ2 = [&, x = x + 10] () {...} ()

 

 

보라색 부분이 const 로 정의된 부분인다. 

const 러 선언된 변수는 컴파일 후 메모리로 올라 가게 되는데 이때 data영역으로 들어가게 된다. 

따라서 const 로 선언된 변수는 실행중 바뀔 수 없다. 

함수 몸통뒤에 소괄호 () 부분은 람다의 실행을 의미함과 동시에 매개변수를 전달하는 역할을 한다.

 

결과 값이 110이 나온 이유는 람다 함수가 정의 될 때 이미 const 선언에 의해 미리 x값을 갖고 시작하기 때문입니다.

실질적으로 x값이 바뀌는 부분은 16행인 l1() 함수를 호출할 때 이고, 

다음 l2()를 호출해도 초기 값은 실행하기에 앞서 미리 정의되었음으로 변경된 'x = 5' 가 아닌, 초기 값 'x = 10'을 가져오게 됩니다.


람다를 활용하면 함수객체를 간편하게 만들 수 있다. 

 auto IsUniqueLambda = [](Item& item) {

 return item._rarity == Rarity::Unique;

 };

 auto findIt2 = std::find_if(v.begin(), v.end(), IsUniqueLambda);

 auto findIt3 = std::find_if(v.begin(), v.end(), [](Item& item) { return item._rarity == Rarity::Unique;});

 if (findIt3 != v.end()) {

 cout << "아이템ID: " << findIt3->_itemId << endl;

 }

 else {

 cout << "못찾음" << endl;

 }

 }

람다 자체가 함수처럼 자유롭게 사용할 수 있기 때문에 인자로 레퍼런스들도 전달이 가능하다.


예제1)

int total_elements = 1;
vector<int> cardinal;

cardinal.push_back(1);
cardinal.push_back(2);
cardinal.push_back(4);
cardinal.push_back(8);

for_each(cardinal.begin(), cardinal.end(), [&](int i) { total_elements *= i; });

cout << "total elements : " << total_elements << endl;

모든 원소가 곱해져서 나온다. 64가 나온다. 

&이므로 total_elements를 캡쳐 할 수있다.  값을 바꿀수 있다는 의미이다.

 

예제2)

template <typename T>
void fill(vector<int>& v, T done) {
  int i = 0;
  while (!done()) {
    v.push_back(i++);
  }
}

vector<int> stuff;
fill(stuff, [&]() -> bool { return stuff.size() >= 8; });

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