[1일 1블로그 무사고 3일] C++ 람다 표현식
람다
람다는 C++11에서 도입된 개념으로 필요한 위치에 익명 함수나 함수 개체(클로저)를 정의하는 간결한 방법을 제공한다.
보통 람다는 알고리즘이나 비동기 메서드에 전달하는 몇 줄의 코드를 캡슐화하는데 사용된다
람다의 구성
1
2
3
4
5
6
7
8
9
10
11
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
- capture 절(람다 소개자라고도 함)
- 매개 변수 목록(람다 선언자라고도 함)
- 변경 가능한 사양 선택 사항(mutable)
- exception-specification 선택 사항
- 후행 반환 형식 선택 사항
- 람다 본문
Capture 절
C++14에서 람다는 람다의 본문에 새 변수를 도입할 수 있다.
보통 함수에서 접근 가능한 변수는 전달받은 매개변수, 함수가 포함된 class, struct의 멤버 변수, 글로벌 변수 등인데 람다는 람다가 포함된 영역의 변수에 캡처할 수 있다.
람다는 값이나 참조로 변수를 캡처할지와 어떤 변수를 캡처할지를 정하는 캡처 절로 시작된다.
캡처란? 람다의 바디에서 어떤 변수를 어떻게 사용할지를 나타내는 것임(값으로 참조하는 경우, 변수의 값을 변경할 수 없다)
- [] : 같은 영역에 있는 모든 변수에 접근 불가
- [&] : 같은 영역에 있는 모든 변수를 참조로 캡처
- [=] : 같은 영역에 있는 모든 변수를 값으로 캡처
- [&, 변수A] : 같은 영역에 있는 모든 변수를 참조로 캡처. 단 변수 A만 값으로 캡처
- [=, &변수A] : 같은 영역에 있는 모든 변수를 값으로 캡처. 단 변수 A만 참조로 캡처
- [&변수A] : 같은 영역에 있는 변수A만 참조로 캡처. 나머지 다른 변수들에는 접근 불가
- [변수A] : 같은 영역에 있는 변수 A만 값으로 캡처. 나머지 다른 변수들에 접근 불가
간단히 요약해보자면 캡처 절 안에 아무것도 쓰지 않는다면([]) 람다 식 바깥에 있는 변수들을 못 쓴다는것이고, [=]은 바깥에 있는 변수들을 쓰긴 하는데 값만 복사되어 바깥 변수 값을 바꾸지 못하는 것이고 [&]은 바깥에 있는 변수를 참조로 받아와 람다 식에서 바꾸면 바깥 변수도 값이 바뀌는 것이다.
일반화된 캡처
C++14에서는 람다가 포함된 영역에 변수들이 존재해야 할 필요 없이 캡처 조항에 새로운 변수를 도입하고 초기화할 수 있다
초기화는 어느 대체식으로 표현 가능하고 새로운 변수의 타입은 표현식에서 제공하는 타입으로 추론됨
이 특징의 한 가지 장점은 C++14에서 이동만 가능한 변수(ex std::unique_ptr)를 캡처하고 사용할 수 있다는 것임
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
...
std::unique_ptr<A> pA = std::make_unique<A>();
auto f = [ptr = std::move(pA)]
{
// ptr 사용.
ptr->f();
};
...
[출처] 모던 C++ Lambda 표현식(개념, 사용법)|작성자 Chan
}
매개변수 리스트
두 번째 위치에 오는 매개변수 리스트. 캡처한 변수들 외에 람다가 매개변수를 받을 수 있다
선택사항이기에 없으면 ()로 나타내거나 생략해도 무관함. C++14에서 매개 변수 형식이 제네릭인 경우 auto를 쓸 수 있다
람다 식은 다른 람다 식을 인수로 사용할 수도 있다 -> 자세한 내용은 람다 식
변경 가능한 사양 선택(mutable)
일반적으로 람다의 함수 호출 연산자는 const-by-value라서 람다의 바디에서는 값으로 캡처한 변수는 const 변수로 판단함.
그러나 mutable 키워드를 사용하면 값으로 캡처한 변수도 수정이 가능하다. 물론 값으로 캡처했기에 수정은 가능하지만 원본의 변수 값은 바뀌지 않는다.
예외 사양
noexcept 키워드를 사용하면 해당 람다는 어떤 예외도 던지지 않을 것을 명시해 줌.
만약 키워드를 사용했는데 throw를 하게 되면 C4297 경고가 발생함.
후행 반환 형식
람다의 반환 형식은 자동으로 추론된다. 만약 람다의 바디가 하나의 반환문만 존재하거나 값을 반환하지 않는다면 람다 표현식에서 반환 형식은 생략해도 됨
하나의 반환문만 있다면, 컴파일러는 반환 타입을 반환 식에서 추론한다. 값을 반환하지 않는다면 void로 추론함
반환 형식을 명시하고 싶다면 -> 반환 형식을 지정해 주면 됨.
1
2
3
4
// int를 반환하는 람다.
auto rf1 = []() { return 1; }; // 자동으로 int로 추론됨.
auto rf2 = []() -> int { return 1; };
[출처] 모던 C++ Lambda 표현식(개념, 사용법)|작성자 Chan
람다 본문
람다 표현식에서 람다의 본문은 일반 함수의 바디에서 포함할 수 있는 어떤 것도 포함할 수 있다
- 캡처된 변수들
- 매개변수
- 본문 내에 선언된 지역 변수
- 클래스 내부에 선언됐고 this가 캡처됐다면, 클래스의 멤버들
- 전역 변수, static 변수와 같이 접근 가능한 변수들
constexpr 람다 표현식
C++17 이상에서 람다 표현식은 constexpr로 선언될 수 있고, 캡처된 각 데이터 멤버의 초기화가 상수 표현식 안에서 허용될 때 상수 표현식에서 사용될 수 있다
이 부분은 내가 constexpr을 잘 몰라서 이해하기 어려워 GPT를 통해 추가로 찾아봤다. 다음 게시글은 이걸로 가야겠다
constexpr이 표현식이나 함수가 컴파일 타임에 평가될 수 있게 해서 성능 향상을 위해 컴파일 타임에 평가되는 상수 및 함수를 선언하는데 자주 사용된다고 함
람다 식이 constexpr로 선언되면 상수 식이 필요한 상황에서 람다를 사용할 수 있으며, 컴파일 타임에 제공된 인수 자체가 상수 식인 경우 컴파일타임에 평가가 발생할 수 있음을 의미
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
int main() {
constexpr auto square = [](int x) constexpr -> int {
return x * x;
};
constexpr int result = square(5);
std::cout << "Result: " << result << std::endl; // Output: Result: 25
return 0;
}
람다 표현식은 square는 constexpr로 선언되고 constexpr을 반환함. result 변수는 constexpr로 선언되고 인수를 사용해 square를 호출해 할당된다.
람다는 constexpr이므로 제곱 계산은 컴파일 타임에 발생하고 결과는 런타임에 알려짐
constexpr 람다 표현식은 컴파일 시간 평가 가능 함수를 생성하는 방법을 제공해 상수 표현식이 필요한 상황에서 더 많은 유연성과 최적화를 허용함