포스트

C++ 람다식

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)다음 조건을 만족하는 값(객체)을 의미합니다.

  1. 변수에 저장 가능 (함수나 객체를 변수에 할당할 수 있음)
  2. 함수의 인자로 전달 가능 (함수를 다른 함수의 매개변수로 전달 가능)
  3. 함수의 반환값으로 사용 가능 (함수가 함수를 반환 가능)
  4. 동적으로 생성 가능 (실행 중에 함수나 객체를 생성할 수 있음)

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++에서 활용 가능.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.