[C++ 공부] 람다 함수

Yujin Lee·2022년 2월 19일
0

Cpp_Study

목록 보기
3/8
post-thumbnail

내가 과제로 작성했던 코드를 gerrit에서 보시고 람다 함수를 쓰셨네요? 라고 말씀하시던 선임님...

녱? 그게뭔데용?


1. 람다 함수

람다식은 함수 객체를 조금 더 짧게 작성하자 는 취지로 등장했다.
lambda는 익명 함수로도 불린다. 함수 객체와 비슷한 성질이나 이름이 없는 함수이다.
프로그래밍을 하다 보면 함수 포인터함수 객체가 필요할 때가 있다. 람다는 클래스나 구조체를 따로 정의할 필요가 없어서 편리하다.

[캡처_블록](파라미터_목록) mutable 익셉션_목록 → 리턴_타입 { 함수_바디 }
app_->register_state_handler(
    [](vsomeip_v3::state_type_e _state) {std::cout << static_cast<int>(_state) << std::endl;}
    );

2. 람다 함수의 기본 구문

간단히 말해 람다 함수의 기능은 프로그래머가 변수를 마치 전달하듯이 함수를 정규 함수에 전달하게 해준다.
변수와 반드시 연계될 필요는 없고, 단지 구문에서 변수를 오른쪽에 놓으면 된다.


아래는 간단한 람다 함수의 기본 구문 예제이다.

auto vals = std::vector<int>{1, 3, 2, 5, 4};

// 숫자 3은 어디에 있나?
auto num_3 = std::count(vals.begin(), vals.end(), 3);

// 3보다 큰 수는 몇 개?
auto num_above_3 = std::count_if(vals.begin(), vals.end(), [](int v){
	return v > 3;
});

3. 캡처

람다에 외부 변수를 사용하려면 캡처라는 개념을 사용한다. [ ] 형태로 표시된 캡처 블록 에 외부 변수를 넣어 캡처한다. 캡처 블록은 클래스 멤버 변수와 생성자의 조합이다.

예를 들어 velog라는 변수를 람다 함수에 복사하면서 캡처하려면

auto count_value_above(const std::vector<int>& vals, int velog) {
	auto is_above = [velog](int v) { return v > velog; };
    return std::count_if(vals.begin(), vals.end(), is_above);
}

근데 velog를 참조로 선언하려면 & 기호를 쓰면 된다.

	auto is_above = [&velog](int v) { return v > velog; };

3.1 mutable

람다 함수는 const가 기본이다. 따라서 멤버 변수를 변경하려면 mutable 을 지정해야 한다.

auto lambda = [var]() mutable {
	...
};

3.2 다양한 캡처 방법

class ivis {
public:
	auto member_function() {
    	auto a = 0;
        auto b = 1.0f;
        
        // 복사로 모든 변수 캡처
        auto lambda_0 = [=]() { std::cout << a << b << m_; };
        // 참조로 모든 변수 캡처
        auto lambda_1 = [&]() { std::cout << a << b << m_; };
        // 참조로 멤버 변수 캡처
        auto lambda_2 = [this]() { std::cout << m_; };
        // 복사로 멤버 변수 캡처
        auto lambda_3 = [*this]() P std::cout << m_; };
    }

private:
	int m_{};

};

[this]() 는 몇 번 봤다. 근데 나머지는 진짜 쓰는건가...?


4. std::function

모든 람다 함수는 자신만의 타입을 가지고 있다. 따라서 똑같은 함수라도 다른 람다 함수에 할당될 수 없다. 하지만 std::function은 동일한 서명을 갖고 있는 어떤 람다 함수라도 보유할 수 있다.

std::function은 클로저 객체( 함수 객체, 함수 포인터, 람다 식 )를 저장할 수 있다.

사용하려면 헤더에 functonal을 include해야 한다.

#include <functional>

이런 식으로 사용한다.

예를 들어, std::function이 bool을 반환하고 int와 std::string 파라미터를 가지면 다음과 같이 정의한다.

// std::function<리턴타입(인자)>
auto func = std::function<bool(int, std::string)>{};

4.1 람다와 비교할 때 std::function의 단점

  • std::function은 인라인으로 쓸 수 없다.
    컴파일러가 std::function으로 감싼(wrapped) 함수의 인라인 호출이 거의 불가능하다. 따라서 감싼 함수의 호출이 잦을 경우 성능에 영향을 줄 수 있다.
  • 호출 시 약간의 추가 동작이 필요하다.
    std::function 호출은 일반적으로 추가 코드가 포함된다. 람다를 실행하는 것보다 약간 느리다.

4.2 하지만 그럼에도 사용하는 이유

: 순환 참조가 가능하다.

func1 과 func2가 서로를 사용해야할 때,

auto func1 = [&func2]() {func2(); };    // error, func2가 선언되어 있지 않아요!
    auto func2 = [&func1]() {func1(); }; 

auto는 미리 정의해두지 않으면 사용할 수 없는 반면,


std::function <void(void)> func1;
std::function <void(void)> func2 = [&func1]()->void{ func1(); };
func1 = [&func2]()->void{func2(); };

std::function은 미리 functor를 선언만 해놓고 나중에 정의할 수 있다.

profile
I can be your Genie🧞‍♀️ How ‘bout Aladdin? 🧞‍♂️

0개의 댓글