Programming/_C++

_C++_lambda

Player_blue 2023. 9. 20. 18:02

람다(lambda)는 익명의 함수를 정의하는 표현식을 의미한다. 람다를 통해 클로져(Closure) 클래스가 정의됨.

람다는 캡처(capture), 인자(parameter), 반환형(return type), 몸통(body)로 구성되었다.

//case 1
auto f = [captures](parameters) -> return type {
	body
    /*
    captures: 캡처들이 들어감
    parameters: 함수의 인자들이 들어감
    return type: 함수의 반환형
    body: 함수의 몸통    
    */
}

//case 2
#include <functional>
function<void(int)> g = [](int param) {
	//function 템플릿을 사용하여 형식 지정이 가능하다.
};

위의 두 구조중 1번 구조가 효과적이다.

내부를 알 수 없는 경우 auto로 컴파일러가 결정할 수 있게 하는 것이 효과적이다.

우리가 형식을 지정한다면, 메모리가 부족할 수 있어서 효과적이지 않다.

 

람다는 위와 같은 구조를 가지며, return type의 명시는 생략가능하다. 생략한다면, void 타입으로 추론한다.

만약, return문이 여러번 등장하는데 return type이 일치하지 않으면 return type을 생략할 수 없다.

 

1. capture

 다른 프로그래밍 언어에서는 capture를 정해주지만, C++은 사용자가 정해야 한다. (이는 장점이자 단점?)

capture문은 함수 몸체에서 외부 변수에 접근하기 위해 사용된다.

capture할 이름 앞에 '&'를 붙이면 참조로 capture하고, 그렇지 않으면 값으로 capture한다.

값으로 capture를 한다면, const로 취급된다.

#include <iostream>
using namespace std;

int main()
{
    int x = 0, y = 1;
    auto f = [&x, y](){ //&x는 참조, y는 값으로 캡쳐
        return x += y;
    };
    return 0;
}

capture는 댕글링 포인트 버그를 발생할 가능성이 있으므로 유의하자!!

// 간단한 예시
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    []() { std::cout << "Hello lambda!" << std::endl; }();

    int sum = 0;
    vector<int> nums {1, 2, 3};
    std::for_each(nums.begin(), nums.end(), [&sum](int& number) { sum += number; });
    cout << sum;
    return 0;
}

 

기본 캡쳐 모드

기본 캡쳐 모드는 스코프 내의 모든 이름을 일관적으로 capture하는 것이다.

auto f = [&](){ //기본 캡쳐 모드는 참조
};

auto g = [=](){ //기본 캡쳐 모드는 값
};

auto h = [&, y](){//기본 캡쳐 모드는 참조이고 y는 값으로 캡쳐
};

위와 같이 기본 capture 모드를 정하고 추가되는 capture는 다른 방법으로 할 수 있다.

기본 capture 모드는 캡쳐할 대상을 나열하지 않기 때문에, capture 내용을 제대로 파악하지 않을 수 있으므로 외부의 변화와 무관하게 자기 완결적으로 오해하기 쉽다고 한다.

또한 전역 변수, 같은 블록 스코프 내의 static 변수들은 캡쳐가 불가능하다고 한다.(할 필요도 없음)

우리가 기본 값 캡쳐모드를 사용하면서 이러한 전역 변수, static 변수들이 값으로 복사되었다고 생각할 수 있다.

 

초기화 캡쳐(학습중)

이동 전용 객체의 경우 복사가 삭제되어 값 캡쳐가 불가능 하다. 참조 캡쳐는 가능하지만, 참조 대상 객체가 먼저 소멸 하는 경우가 있다고 한다.

초기화 캡쳐는 C++14에서 추가되었으며, 위와 같은 경우에서 사용된다고 한다.

 

만약, 클로져 자체가 재귀 함수라면, function으로 선언해야 한다고 한다.(auto로 선언하는 것이 불가능)

#include <functional>

using namespace std;

int main()	[
    auto f = [&f](){ // 컴파일 에러 발생
    	f();
    };
    
    function<void()> g = [&g](){
    	g();
    };
	return 0;
}

auto로 선언을 하게 되면 컴파일 시점에서 f의 형식이 불완전하게 되어 capture가 불가능 하다고 한다.