Lambda 初始化捕获

为什么需要初始化捕获?

在C++11中,Lambda表达式的捕获列表有一定的局限性:

  • 只能捕获当前作用域内存在的变量(通过值 [=] 或引用 [&])。
  • 只能直接捕获变量名,无法在捕获时执行表达式(比如移动操作、调用函数初始化等)。

这导致了一些问题,例如:

  • 无法移动捕获(Move Capture):无法轻易地将一个只能移动(move-only)的类型(如 std::unique_ptr)捕获到Lambda中。在C++11中,这需要通过 std::bind 等技巧来实现,非常繁琐。
  • 无法用任意表达式初始化捕获成员:你不能在捕获一个成员变量时,先对其进行一些计算再捕获结果。

C++14的初始化捕获就是为了解决这些问题而生的。它允许你使用任意表达式来初始化Lambda的捕获成员,从而极大地增强了灵活性和表现力。

语法格式

[数据成员 = 初始化表达式](参数列表) -> 返回值类型
{ 
    函数体 
}

// 在捕获时指定一个新名字
[新变量名 = 已存在变量名](参数列表) -> 返回值类型
{ 
    函数体 
}

具体用法和示例

移动捕获(Move Capture)

 

在C++11中要实现移动捕获,需要借助 std::bind 方法,例如:

std::unique_ptr<A> ptr = std::make_unique<A>();

auto cbk = std::bind([](const std::unique_ptr<A>& my_ptr)
    {
        my_ptr->do_something();
    }, std::move(ptr));

// 注意:执行 std::move(ptr) 后,外部的 ptr 为 empty

C++14的优雅做法:

std::unique_ptr<A> ptr = std::make_unique<A>();

auto cbk = [my_ptr = std::move(ptr)]()
{
    my_ptr->do_something();
};
// 注意:执行 std::move(ptr) 后,外部的 ptr 为 empty

按值捕获并重命名

有时想用一个更清晰的名字来捕获变量,可以对捕获的变量重命名:

std::string very_long_descriptive_external_name = "Hello";

// 捕获外部变量,但在Lambda内部使用一个简短的名字
auto lambda = [greeting = very_long_descriptive_external_name] 
{
    std::cout << greeting << std::endl; // 内部使用 `greeting`,更简洁
};

用表达式初始化捕获成员

可以在初始化表达式中进行计算:

int x = 10;
int y = 20;

// 捕获的不是 x 或 y,而是 x + y 的计算结果
auto lambda = [sum = x + y] 
{
    std::cout << "The sum is: " << sum << std::endl; // 输出 30
};

// 甚至可以用函数返回值来初始化
auto get_value = []() -> int { return 42; };
auto lambda2 = [value = get_value()]           // 调用函数初始化
{ 
    std::cout << value << std::endl;
};

捕获成员变量的“快照”

在非静态成员函数中,直接捕获成员变量实际上捕获的是 this 指针。如果Lambda的生命周期比对象长,这会导致悬空指针(dangling pointer)。使用初始化捕获可以捕获成员变量的一个副本或移动后的状态,从而避免这个问题。

C++11方式:

class MyClass
{
    std::vector<int> m_data;

    void register_callback()
    {
        // 危险!捕获了 `this`,如果对象被销毁,Lambda再运行就会出错
        auto bad_lambda = [this]()
        {
            use(m_data);
        };
    }
};

安全的C++14方式:

class MyClass 
{
    std::vector<int> m_data;

    void register_callback() 
    {
        // 安全!将 m_data 移动捕获到Lambda内部,与原对象生命周期解耦
        auto safe_lambda = [data = std::move(m_data)]() 
        { 
            use(data);
        };

        // 或者,如果你不想移动原数据,可以创建一个副本
        /*
        auto safe_lambda = [data_copy = m_data]()
        {
            use(data_copy);
        };
        */
    }
};
posted @ 2026-04-13 16:12  西兰花战士  阅读(5)  评论(0)    收藏  举报