람다 펑션 Lambdas function (anonymous functions)
람다 펑션 - Lambdas function
람다 펑션은 실무에서 많이 사용되는 것중 하나로 아주 중요 합니다.
아래 예제는 std::string의 find_if()를 통해 string array 에서 "nut"을 포함 하는 string을 찾는 예제 입니다.
#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>
bool containsNut(std::string str)
{
// 매게변수로 받은 str값에서 nut을 발견 하면 True 를 리턴한다.
return (str.find("nut") != std::string::npos);
}
int main()
{
const std::array<std::string, 4> arr{ "apple", "banana", "walnut", "lemon" };
// std::find_if 의 세번째 인자는 포인터 펑션을 받는다.
const auto found = std::find_if(arr.begin(), arr.end(), containsNut) ;
if (found == arr.end())
{
std::cout << "No nuts\n";
}
else
{
std::cout << "Found " << *found << '\n';
}
return 0;
}
여기서 문제는 std::find_if() 에 함수 포인터 (function pointer) 를 전달 해야 합니다. 그러기 위해서 펑션을 무조건 정의해야 합니다. 한번의 사용을 위해 펑션을 정의하는것은 유지보수 측면에서 힘들다.
이러한 문제를 해결 하기 위해 나타난 람다펑션 - Lambdas to the rescue
lambda expression (람다 또는 클로저라고도 부름)을 사용하면 다른 함수 내에 익명 함수 (anonymous functions)를 정의 할 수 있다.
람다 구문은 c++ 의 다른 구문과 비교해 봤을때 조금 낯설어서 익숙해 지는데 다소 시간이 걸리지만, 계속적으로 사용을 하다 보면 금방 익숙해 질 수 있다. Lambda는 다음과 같은 형식을 취합니다.
[ captureClause ] ( parameters ) -> returnType
{
statements;
}
여기서 capture clause 와 parameters 는 생략 될 수 있습니다. 또한 리턴타입 (return type) 은 optional 이며 만약 생략될 경우 auto 타입이 사용됩니다. 마지막으로 람다펑션의 이름 또한 생략 될 수 있습니다. 종합해 보면 람다 펑션은 아래와 같이 구현 할 수 있습니다.
#include <iostream>
int main()
{
[]() {}; // 캡쳐, 파라미터와 리턴 타입이 생략 되었다.
return 0;
}
위에 첫번째 예제를 람다 펑션을 이용하여 재 구현 해보자.
#include <algorithm>
#include <array>
#include <iostream>
#include <string_view>
int main()
{
constexpr std::array<std::string_view, 4> arr{ "apple", "banana", "walnut", "lemon" };
const auto found{ std::find_if(arr.begin(), arr.end(),
[](std::string_view str) // 람다펑션, no capture clause
{
return (str.find("nut") != std::string_view::npos);
}) };
if (found == arr.end())
{
std::cout << "No nuts\n";
}
else
{
std::cout << "Found " << *found << '\n';
}
return 0;
}
위 예제를 실행하게 되면 첫번째 펑션포인터를 사용한 결과와 동일하다.
람다 펑션 유형 - Type of a lambda
위 예제에서 람다 펑션을 find_if 함수 안에 정의 하였다. (람다펑션의 시작은 항상 capture clause 으로 시작 되기 때문에[] 보이면, 아!! 이것은 람다펑션이구나!! 생각을 해주면 된다.) 이러한 사용 방식을 함수 리터럴 (function literal) 이라고 한다. 위와 같이 함수 안에 람다를 정의를 하게 되면 코드의 가독성이 떨어질 수 있다. 이러한 문제를 해결 하기 위해 우리는 람다 변수를 정의 한 후 람다 펑션을 사용 할 수 있다.
예를 들어, std::all_of 함수를 사용하여 배열의 모든 요소가 짝수인지 확인합니다. 아래 예제는 std::all_of 펑션 안에 람다를 정의 하였다.
// 코드의 가독성이 떨어지는 좋지 않은 example
return std::all_of(array.begin(), array.end(), [](int i){ return ((i % 2) == 0); });
위 코드는 아래와 같이 바꿀 수 있습니다.
// isEven 변수에 람다를 저장 한다.
auto isEven {
[](int i)
{
return ((i % 2) == 0);
}
}
return std::all_of(array.begin(), array.end(), isEven);
여기서 람다의 타입은 무엇일까? 람다의 타입은 컴파일시 컴파일러가 유니크한 타입을 만들어 준다.
람다의 타입은 컴파일 타임까지 알 수는 없지만, 람다를 저장하기 위한 방법은 아래와 같이 여러 가지가 있다.
Capture clause가 생략되었을 때 함수펑션 (function pointer)를 사용할 수 있다.
// A regular function pointer. Only works with an empty capture clause.
double (*addNumbers1)(double, double) {
[](double a, double b) {
return (a + b);
}
};
std::cout << addNumbers1(1, 2) << std::endl;
Capture clause가 사용될 경우 std::function을 이용하여 람다를 정의 할 수 있다.
// Using std::function.The lambda could have a non - empty capture clause(Next lesson).
int aa = 10;
std::function<int(double, double)> addNumber2;
addNumber2 = [aa](double a, double b) {
return (aa + a + b);
};
addNumber2(3, 4);
위 예제를 실행시 17이 출력 된다.
마지막으로 auto를 사용하여 람다를 정의 할 수 있다.
// Using auto. Stores the lambda with its real type.
auto addNumbers3{
[](double a, double b) {
return (a + b);
}
};
addNumbers3(5, 6);
람다의 실제 유형을 사용하는 유일한 방법은 auto keyword을 사용하는 것입니다. auto는 std::function에 비해 오버 헤드가 없다는 이점도 있습니다.
하지만 람다를 정의할때 항상 auto 을 사용 할 수 있는 것은 아니다. 예를 들어 람다를 매개변수로 함수에 전달하고 호출자가 전달하는 람다의 타입을 결정 하기 때문에 auto를 사용 할 수 없다. 이러한 경우엔 std::function을 사용하여 정의 할 수 있다.
#include <functional>
#include <iostream>
// We don't know what fn will be. std::function works with regular functions and lambdas.
void repeat(int repetitions, const std::function<void(int)>& fn)
{
for (int i = 0; i < repetitions; ++i)
{
fn(i);
}
}
int main()
{
repeat(3, [](int i) {
std::cout << i << '\n';
});
return 0;
}
위 예제는 다시 아래와 같이 바꿀 수 있다. using 키워드와 std::function 의 예는 melbourne-engineer.tistory.com/3?category=807213
using printI = const std::function<void(int)>;
void repeat(int repetitions, printI fn) {
for (int i = 0; i < repetitions; i++) {
fn(i);
}
}
int main()
{
repeat(3, [](int i) {
std::cout << i << '\n';
});
return 0;
}
이포스트의 원문은 www.learncpp.com/cpp-tutorial/introduction-to-lambdas-anonymous-functions/