[C++20 새기능 소개] 개선된 람다 캡처 (Lambda Capture)

C++20에서는 람다 표현식(lambda expressions)이 더욱 강력해지고 유연해졌습니다. 이번 글에서는 개선된 람다 캡처(Lambda Capture)와 관련된 새로운 기능들을 살펴보겠습니다.

개선된 람다 캡처란?

C++11에서 도입된 람다 표현식은 익명 함수 객체를 생성하여 함수처럼 사용할 수 있게 해주는 기능입니다. 기존의 람다 캡처 방식에서는 [=], [&], [this] 등의 캡처 모드를 사용했습니다. C++20에서는 람다 캡처가 더욱 개선되어, [=, this], [*this]와 같은 새로운 캡처 방식이 도입되었습니다. 이를 통해 람다 표현식 내에서 멤버 변수를 안전하고 효율적으로 사용할 수 있습니다.

[*this]를 사용한 객체 복사 캡처

기존의 문제점

기존의 [=] 또는 [this] 캡처 방식은 람다 표현식 내에서 this 포인터를 복사하거나 참조합니다. 하지만 this 포인터는 객체를 참조하므로, 객체가 소멸된 후에 람다를 호출하면 미정의 동작(undefined behavior)이 발생할 수 있습니다.

[*this]를 사용한 개선

[*this] 캡처를 사용하면 현재 객체를 복사하여 람다 표현식에 캡처합니다. 이를 통해 객체의 수명과 관계없이 안전하게 람다를 사용할 수 있습니다.

사용 예제

#include <iostream>
#include <string>
#include <functional>

class Person {
public:
    Person(std::string name) : name_(name) {}

    auto getGreeter() const {
        return [*this]() {
            std::cout << "안녕하세요, " << name_ << "입니다.\n";
        };
    }

private:
    std::string name_;
};

int main() {
    auto greeter = Person("홍길동").getGreeter();
    greeter(); // 출력: 안녕하세요, 홍길동입니다.
    return 0;
}
  • [*this]를 사용하여 Person 객체를 복사하여 캡처합니다.
  • Person 객체가 소멸된 후에도 람다 내에서 안전하게 name_ 멤버에 접근할 수 있습니다.

[=, this]를 사용한 멤버 접근

기존의 제한 사항

기존의 [=] 캡처 모드는 람다 표현식 내에서 지역 변수를 복사로 캡처합니다. 그러나 클래스 멤버에 접근하려면 this 포인터가 필요하며, [=]로는 this를 캡처하지 않습니다.

[=, this]를 사용한 개선

C++20에서는 [=, this] 캡처를 사용하여 지역 변수는 복사로, this 포인터는 참조로 캡처할 수 있습니다.

사용 예제

#include <iostream>

class Counter {
public:
    Counter(int start) : count_(start) {}

    void startCounting(int limit) {
        int step = 1;
        auto counter = [=, this]() {
            for (; count_ <= limit; count_ += step) {
                std::cout << "현재 값: " << count_ << '\n';
            }
        };
        counter();
    }

private:
    int count_;
};

int main() {
    Counter c(0);
    c.startCounting(3);
    return 0;
}
  • [=, this]를 사용하여 step 변수는 복사로 캡처하고, this 포인터는 참조로 캡처합니다.
  • 람다 내에서 count_ 멤버 변수에 접근할 수 있습니다.

람다의 템플릿 사용

람다 표현식의 템플릿 매개변수

C++20에서는 람다 표현식이 템플릿 매개변수를 가질 수 있게 되었습니다. 이를 통해 더욱 일반화된 람다 함수를 작성할 수 있습니다.

사용 예제

#include <iostream>

auto adder = []<typename T>(T a, T b) {
    return a + b;
};

int main() {
    std::cout << adder(1, 2) << '\n';         // 출력: 3
    std::cout << adder(1.5, 2.5) << '\n';     // 출력: 4.0
    std::cout << adder(std::string("Hello, "), std::string("World!")) << '\n'; // 출력: Hello, World!
    return 0;
}
  • 람다 표현식에 템플릿 매개변수 <typename T>를 추가하여 다양한 타입에 대해 동작합니다.

constexpr 람다

람다의 constexpr 지원 강화

C++20에서는 람다 표현식이 constexpr로 선언되면, 람다의 호출 연산자도 constexpr이 됩니다. 이를 통해 컴파일 타임에 람다를 평가할 수 있습니다.

사용 예제

constexpr auto square = [](int x) {
    return x * x;
};

int main() {
    constexpr int result = square(5);
    static_assert(result == 25, "결과가 25가 아닙니다.");
    return 0;
}
  • constexpr 람다를 사용하여 컴파일 타임에 값을 계산하고, static_assert로 검증합니다.

noexcept 람다

람다의 noexcept 지정

C++20에서는 람다 표현식에 noexcept를 지정할 수 있습니다.

사용 예제

auto safe_divide = [](double a, double b) noexcept -> double {
    return b != 0 ? a / b : 0.0;
};

int main() {
    std::cout << safe_divide(10.0, 2.0) << '\n'; // 출력: 5.0
    std::cout << safe_divide(10.0, 0.0) << '\n'; // 출력: 0.0
    return 0;
}
  • noexcept를 사용하여 람다가 예외를 throw하지 않음을 명시합니다.

요약

C++20에서의 개선된 람다 캡처와 관련 기능들은 람다 표현식을 더욱 강력하고 유연하게 만들어 줍니다. 특히 [*this]와 [=, this] 캡처 방식은 클래스 멤버 변수의 안전한 사용을 가능하게 하며, 람다의 템플릿 매개변수 지원은 제네릭 프로그래밍을 더욱 편리하게 해줍니다.

 

 

참고 자료:

 

반응형