C++ 람다 표현식

Seongcheol Jeon·2024년 12월 7일
0

CPP

목록 보기
34/47
post-thumbnail

람다(lambda) 표현식이란?

람다 표현식람다식, 람다 함수로도 부르지만, C++에서 정확한 용어는 람다 표현식(lambda expression)이다. 함수형 언어나 파이썬 같은 인터프리터를 사용하는 언어에서 사용되던 클로저(closure)가 네이티브 프로그래밍 언어에 도입되면서 람다 함수, 람다 표현식이 되었다.

C++언어에서 람다 표현식함수를 단순하게 만들고 로직을 이해하기 쉽게 만들어 준다. 공통 기능을 함수로 만들면 여러 상황을 고려해야 하므로 예외 처리, 매개변수에 따른 분류등 부수적인 코드가 필요하다.
하지만 람다 표현식을 즉시 호출 형태로 사용하면 코드의 위치가 곧 호출 위치이므로 로직을 한눈에 파악할 수 있으며, 인라인(inline) 함수로 만들어 성능을 최적화하는 데도 도움이 된다.

람다가 특별히 유용한 경우는 다음과 같다.

  • 한 번만 호출하고 재사용하지 않기 때문에 함수에 이름을 붙일 필요가 없는 경우.
  • STL 알고리즘 함수의 매개 변수에 연산 코드를 넘기는 경우, 연산 코드를 익명의 람다식으로 작성.

즉시 호출 형태로 사용하기

람다 표현식은 다양한 형태로 사용되지만, 기본 구조는 정의부를 포함해 크게 5개로 구성한다. 이를 코드로 나타낸 예를 살펴보자.

다음 코드는 거스름돈을 반환하는 람다 표현식즉시 호출 형태로 표현한 예이다.

[&changes](int payment, int price) -> int {
	int change = payment - price;
    changes[0] = change / 1000;
    change %= 1000;
    return payment - price;
} (payments[i], price[i])

매개변수 목록과 정의부는 일반 함수와 같다. 다른 점은 이름이 없다는 것과 외부 변수를 캡쳐하는 부분, 그리고 반환값의 데이터 형식을 화살표 연산자(->) 다음에 명시한다는 점이다. 그리고 람다 함수를 즉시 호출 형태로 사용하려면 정의부 다음에 호출 연산자와 함께 매개변수에 맞춰 인자를 전달한다.

함수 이름이 없다는 특성에 따라 람다 함수를 익명 함수(anonymous function)라고도 한다.

람다 표현식은 이를 선언한 함수의 범위에서 사용되지만, 하위 함수로 선언되므로 상위 함수의 지역인 선언부의 변수에는 접근할 수 없다. 외부 변수 캡쳐상위 함수에 선언된 지역 변수를 람다 표현식 내에서 사용할 수 있게 해준다.

외부 변수를 캡쳐하는 방법은 다음 표에서 확인할 수 있다. 표에서 선언부 범위란, 람다 표현식이 선언된 함수를 의미한다. 예를 들어 main 함수에서 람다 함수를 선언했다면 main 함수에 선언된 지역 변수를 컙쳐한다. 만약, main 함수에서 생성한 객체의 멤버 함수에서 람다 표현식을 선언했다면 해당 객체의 멤버 함수에서 선언된 지역 변수를 캡쳐한다.

캡쳐 방법
캡처되는 변수
람다 표현식 내 사용
[=]선언부 범위의 모든 변수읽기 전용으로 사용
[&]선언부 범위의 모든 변수참조 형식으로 사용되어 읽기와 쓰기 가능
[변수1]변수1변수1을 읽기 전용으로 사용
[변수1, 변수2]변수1, 변수2변수1, 변수2를 읽기 전용으로 사용
[&변수1, 변수2]변수1, 변수2변수1은 참조 형식으로, 변수2는 읽기 전용으로 사용
[=, &변수1]선언부 범위의 모든 변수변수1은 참조 형식으로, 나머지는 읽기 전용으로 사용
[&, 변수1]선언부 범위의 모든 변수변수1은 읽기 전용으로, 나머지는 참조 형식으로 사용

= (읽기 전용 캡처) 이나 & (참조 캡처)를 사용하면 선언부 범위의 지역 변수를 모두 캡쳐할 수 있으며, 이와 함께 지역 변수 이름을 나열하면 전체 지역 변수 캡처와 반대되는 방식(읽기 전용 <-> 참조)으로 캡처된다.

함수 객체로 사용하기

람다 표현식함수 객체로 사용할 때는 호출부를 작성하지 않는다. 함수 객체로 선언한 람다 표현식은 호출이 필요할 때 함수 객체를 호출해서 사용한다.

// 함수 객체로 사용하는 형태
auto calcu_changes = [&changes](int payment, int price) -> int {
	int change = payment - price;
    changes[0] = change / 1000;
    change %= 1000;
    return payment - price;
}

함수 객체는 다른 함수나 객체에 매개변수로 전달할 수 있다. 그런데 변수를 캡쳐했을 때는 범위를 벗어나서 뎅글링 포인터(dangling pointer)가 될 수 있으므로 람다 표현식을 인자로 전달할 때는 변수를 캐쳐하지 않는 것이 좋다.

렝글링 포인터(dangling pointer)란?

렝글링 포인터는 이미 해제된 메모리의 주소가 포인터에 저장된 경우를 의미한다. 이미 해제된 메모리이므로 다른 포인터가 사용하거나 쓰레기 값이 담겨 있을 수 있다.

람다 표현식에서 this 포인터 사용하기

람다 표현식에서 클래스의 멤버 변수를 사용하고자 할 때 this 포인터를 캡쳐하는 방법이 있다.
=이나 &로 전체 변수를 캡처하면 this 포인터도 함께 캡쳐된다. 캡쳐한 변수를 사용할 때는 this를 명시하면 된다.

[&](int payment) -> int {
	int change = payment - this->price[i];
    ...
    return payment - this->price[i];
}

람다 표현식 추가 선택 사항

지금까지 알아본 람다 표현식에서는 다양한 방법으로 외부 변수를 캡처해 사용할 수 있다. 그런데 만약 외부 변수를 람다 표현식 내에서만 변경해서 사용하고 외부에는 영향을 주지 않으려면 어떻게 해야 할까? 참조 형식이나 읽기 전용으로는 어려울 것이다.

이때는 변수나 객체를 복사해서 가져올 수 있는 mutable 키워드를 이용한다.

람다 표현식을 선언할 때 mutable 키워드를 이용하면 람다 표현식내에서는 변경할 수 있지만, 외부 변수에는 영향을 미치지 않도록 변수가 복사된다.

즉, 읽기 전용도 아니고 참조 형식도 아니므로 변경 가능하면서 외부에는 영향을 미치지 않는다. mutable 키워드는 매개변수 목록 다음에 추가하는 선택 사항이다. 만약 this 포인터도 복사해야 한다면 *this로 캡처하면 된다.

또한 람다 표현식 내부에서 예외 사항일 발생했을 때 동적 예외 처리 식별자를 사용할 수 있다. 예외 처리를 나타내는 throw-> 연산자 앞쪽에 추가할 수 있다.

[&changes] (int payment, int price) mutable throw() -> int {
  ...
} (payments[i], price[i])
  • [&changes]
    • 외부 변수 캡처
      • 람다 표현식 내에서 선언부의 변수를 사용할 수 있게 해준다.
  • (int payment, int price)
    • 매개변수 목록
      • 람다 표현식의 매개변수를 정의하는 부분이다.
  • mutable
    • 복사 캡쳐 여부
      • 읽기 전용이나 참조 형식이 아니라 람다 함수 내부에서만 값을 변경하고 싶을 때 사용한다.
      • 값에 의한 호출(call by value)처럼 사용할 때 지정한다.
  • throw()
    • 예외 처리
      • 예외가 발생할 때 람다 표현식을 감싸는 try, cacht 구문에 예외를 전달한다.
  • -> int
    • 반환 형식
      • 람다 표현식이 반환하는 값의 데이터 형식을 화살표 연산자(->) 뒤에 작성한다.
  • ...
    • 정의부
      • 람다 표현식의 본문이다.
  • (payments[i], price[i])
    • 호출부
      • 람다 표현식을 즉시 호출 형태로 작성할 때 매개변수에 전달할 인자를 나열한다.

0개의 댓글