C++ 람다식
C++ 람다식(Lambda Expression)
1. 람다식이란?
C++의 람다식(lambda expression) 은 익명 함수(anonymous function) 를 정의하는 기능입니다. 기존에는 함수 객체(functor)나 std::function을 사용해야 했던 간단한 연산을 더 쉽게 구현할 수 있도록 도와줍니다.
2. 람다식 기본 문법
기본 구조
1
[캡처](매개변수) -> 반환타입 { 실행문 };
| 요소 | 설명 |
|---|---|
[] | 캡처 리스트(Capture List) - 외부 변수 접근 방식 지정 |
() | 매개변수(Parameter) - 일반 함수처럼 사용 가능 |
-> 반환타입 | 반환 타입(Return Type) (생략 가능, 자동 추론됨) |
{ 실행문 } | 람다 본체(Body) |
기본 예제
1
2
3
4
5
6
#include <iostream>
int main() {
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(3, 5); // 8
}
익명 함수 []로 시작하며, 함수 포인터나 std::function 없이 즉석에서 함수 생성 가능.
3. 람다식이 도입된 이유
람다식이 도입되기 전에는 함수 포인터와 함수 객체(Functor) 를 사용해야 했지만, 코드가 길고 불편한 점이 많았습니다.
기존 방식의 문제점
1. 함수 포인터 방식
1
2
bool isEven(int n) { return n % 2 == 0; }
std::find_if(nums.begin(), nums.end(), isEven); // 함수 포인터 사용
전역 함수로 따로 정의해야 하므로 코드가 분리됨.
2. 함수 객체(Functor) 방식
1
2
3
4
struct IsEven {
bool operator()(int n) { return n % 2 == 0; }
};
std::find_if(nums.begin(), nums.end(), IsEven());
별도의 struct를 정의해야 하므로 코드가 복잡해짐.
3. 람다식 도입 후
1
std::find_if(nums.begin(), nums.end(), [](int n) { return n % 2 == 0; });
불필요한 전역 함수, 구조체 없이 간결한 코드 작성 가능.
4. 람다식의 주요 기능
캡처 리스트(Capture List)
| 캡처 방식 | 설명 |
|---|---|
[=] | 모든 외부 변수를 값(value)으로 캡처 |
[&] | 모든 외부 변수를 참조(reference)로 캡처 |
[x] | x만 값(value)으로 캡처 |
[&x] | x만 참조(reference)로 캡처 |
[=, &y] | 기본적으로 값 캡처, y만 참조 캡처 |
[&, x] | 기본적으로 참조 캡처, x만 값 캡처 |
1
2
3
4
int x = 10, y = 20;
auto lambda1 = [=]() { return x + y; }; // 값 캡처
auto lambda2 = [&]() { x += 5; }; // 참조 캡처
값 캡처 시 원본 변수 변경 불가, 참조 캡처 시 변경 가능.
mutable 키워드
1
2
3
4
int x = 10;
auto lambda = [=]() mutable { x += 5; std::cout << x; };
lambda(); // 15
std::cout << x; // 10 (외부 x는 변경되지 않음)
mutable을 사용하면 값 캡처한 변수도 변경 가능하지만, 외부 변수에는 영향을 주지 않음.
반환 타입 자동 추론
1
auto add = [](int a, int b) { return a + b; }; // 반환 타입 자동 추론
반환 타입을 명시하지 않으면 컴파일러가 자동으로 결정.
5. 람다식의 활용
STL 알고리즘과 함께 사용
1
std::for_each(nums.begin(), nums.end(), [](int n) { std::cout << n * 2 << " "; });
STL과 조합하여 코드 간결화 가능.
std::function과 조합
1
std::function<int(int, int)> add = [](int a, int b) { return a + b; };
std::function을 사용하면 람다식을 함수 객체처럼 저장 가능.
멀티스레딩 활용
1
2
std::thread t([]() { std::cout << "Thread 실행!" << std::endl; });
t.join();
람다식을 스레드에 전달하여 간결한 병렬 처리가 가능.
6. 람다식의 단점
| 단점 | 설명 | |——|——| | 디버깅이 어려움 | 익명 함수라서 스택 트레이스에서 함수 이름이 보이지 않음. | | 가독성이 떨어질 수 있음 | 너무 길어지면 가독성이 나빠짐. | | 성능 저하 가능성 | std::function을 사용할 경우 동적 할당 비용이 발생할 수 있음. | | 객체 수명 관리 문제 | this 캡처 시 객체가 삭제되면 미정의 동작(UB) 발생 가능. |
7. 결론
- 람다식은 기존 함수 포인터, 함수 객체(Functor)의 단점을 해결하기 위해 도입됨.
- STL과의 조합이 훨씬 쉬워지고, 코드가 간결해지면서 가독성이 크게 향상됨.
- 함수 포인터나 Functor보다 직관적이고, 로컬 변수를 캡처할 수 있어 유용함.
- 디버깅이 어렵고, 잘못된 사용 시 성능 저하 및 버그 발생 가능성이 있음.
C++ 람다식과 일급 객체, 일급 함수, 캡처 개념 정리
1. 람다식과 일급 객체(First-Class Citizen), 일급 함수(First-Class Function)의 관계
람다식은 일급 함수(First-Class Function) 개념과 밀접한 관련이 있으며, C++에서 함수를 일급 객체(First-Class Citizen)처럼 다룰 수 있도록 해줍니다. 이를 이해하기 위해 일급 객체와 일급 함수 개념을 먼저 살펴보겠습니다.
2. 일급 객체(First-Class Citizen)란?
정의
프로그래밍 언어에서 일급 객체(First-Class Citizen) 란 다음 조건을 만족하는 값(객체)을 의미합니다.
- 변수에 저장 가능 (함수나 객체를 변수에 할당할 수 있음)
- 함수의 인자로 전달 가능 (함수를 다른 함수의 매개변수로 전달 가능)
- 함수의 반환값으로 사용 가능 (함수가 함수를 반환 가능)
- 동적으로 생성 가능 (실행 중에 함수나 객체를 생성할 수 있음)
C++에서 기본적인 변수(int, double 등)는 일급 객체이며, C++11 이후 함수도 일급 객체처럼 다룰 수 있음.
3. 일급 함수(First-Class Function)란?
정의
일급 함수(First-Class Function) 란 “함수를 변수처럼 다룰 수 있는 개념” 을 의미합니다. 즉, 함수를 값처럼 저장하고, 인자로 전달하며, 반환할 수 있음을 뜻합니다.
(1) 함수 포인터: 가장 기본적인 일급 함수 사용 방식
1
2
3
4
5
6
7
8
void hello() {
std::cout << "Hello, World!\n";
}
int main() {
void (*funcPtr)() = hello; // ✅ 함수 포인터로 저장
funcPtr(); // Hello, World!
}
함수 포인터를 사용하면 함수를 변수처럼 다룰 수 있지만, 사용이 불편함.
(2) std::function: 더 유연한 함수 저장 방식
1
2
3
4
5
6
7
#include <functional>
std::function<int(int, int)> add = [](int a, int b) { return a + b; };
int main() {
std::cout << add(3, 4); // 7
}
std::function을 사용하면 함수 포인터보다 더 유연하게 함수 저장 가능.
4. 람다식과 일급 함수의 관계
람다식은 “일급 함수를 쉽게 사용하기 위한 문법적 도구”
람다식이 도입되기 전에는 함수 포인터, std::function, 함수 객체(Functor) 등을 사용해야 했음. 람다식을 활용하면 일급 함수 개념을 더 쉽게 활용할 수 있음.
(1) 람다식은 변수를 통해 저장 가능 (일급 함수 성질)
1
2
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4); // 7
람다식 자체를 변수에 저장하여, 마치 변수를 다루듯이 함수 호출 가능.
(2) 람다식을 함수 인자로 전달 가능
1
2
3
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n * 2 << " ";
});
람다식을 std::for_each의 인자로 전달하여, 실행할 함수를 동적으로 지정 가능.
(3) 람다식을 반환값으로 사용할 수 있음
1
2
3
std::function<int(int)> multiplier(int factor) {
return [factor](int x) { return x * factor; }; // 람다식을 반환
}
함수가 람다식을 반환하여, 동적으로 새로운 함수를 생성할 수 있음.
5. 람다식과 캡처(Capture) 개념
캡처(Capture)란?
람다식 내부에서 외부 변수를 사용할 수 있도록 저장하는 기능. C++의 람다식은 변수의 스코프(Scope)를 넘어서는 접근이 필요할 때, 외부 변수를 복사하거나 참조로 저장할 수 있도록 “캡처” 기능을 제공함.
캡처라는 용어의 의미
컴퓨터 과학(CS)에서 “캡처(Capture)”는 변수를 람다 또는 클로저(Closure) 내부에서 보존하는 과정을 의미합니다. 이 개념은 클로저(Closure)라는 함수형 프로그래밍 개념에서 유래되었으며, C++에서도 람다식이 외부 변수를 저장할 때 “캡처”라는 용어를 사용합니다.
클로저(Closure)란?
- 함수 내부에서 외부 변수에 접근할 수 있도록 저장하는 기능.
- 함수형 프로그래밍 언어(Python, JavaScript 등)에서 일반적으로 사용됨.
- C++의 람다식도 클로저와 유사한 방식으로 동작하여, 캡처 개념을 사용함.
캡처 방식 예제
1
2
3
4
int x = 10, y = 20;
auto lambda1 = [=]() { return x + y; }; // 값 캡처
auto lambda2 = [&]() { x += 5; }; // 참조 캡처
값 캡처 시 원본 변수 변경 불가, 참조 캡처 시 변경 가능.
6. 결론: 람다식과 일급 객체, 일급 함수, 캡처의 관계
| 개념 | 설명 | 람다식과의 관계 |
|---|---|---|
| 일급 객체(First-Class Citizen) | 변수를 다룰 수 있는 모든 연산을 수행할 수 있는 개체 | 람다식은 변수를 통해 저장, 전달, 반환 가능 |
| 일급 함수(First-Class Function) | 함수를 변수처럼 저장, 전달, 반환 가능 | 람다식은 이를 직관적으로 사용할 수 있게 해줌 |
| 캡처(Capture) | 람다식이 외부 변수를 저장하는 기능 | 람다식 내부에서 외부 변수를 캡처하여 보존 |
- 람다식은 “일급 함수”를 활용하기 위한 편리한 문법 제공.
- 함수를 값처럼 다룰 수 있도록 만들며, 함수 포인터보다 간결한 표현 가능.
- 캡처 기능이 추가됨으로써 함수형 프로그래밍의 클로저(Closure) 개념을 C++에서 활용 가능.