C++中的Lambda函数

本文介绍了C++11中的Lambda函数,它允许在需要时定义,简化了代码复杂度。Lambda可以捕获局部变量,通过拷贝或引用。文中详细讲解了不同类型的Lambda表达式,包括无参数、指定返回类型、捕获局部变量等,并举例说明其在多线程编程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍与引入

最近在阅读C++ Concurrency in Action 2rd edition 以学习C++的多线程特性,发现许多地方都使用了Lambda函数,因此就附录里的内容做一些笔记。

Lambda函数是C++11标准中新增的一个特性,允许在需要使用的时候才进行定义,这种语法糖的特性大大简化了代码复杂度,在某些时候特别有用。其语义可以用来快速地表示可访问的变量,而非使用类中函数来对成员变量进行捕获。

基本的Lambda表达式

Lambda表达式最简单的形式,便是一个self-contained函数,其没有输入参数,仅仅依赖全局变量与函数,甚至可以没有返回值。这样的lambda表达式是一系列由大括号包裹的语句,并且以一对方括号[]作为前缀(称为lambda introducer)。

[]{ // Lambda表达式以[]开始
    do_stuff();
    do_more_stuff();
}(); // 表达式结束,可以直接调用

在这个例子中,表达式通过后面的()调用,不过这种方式不常见。如果你要直接调用,直接写就是了,不必再通过lambda表达式。更加常见的用法是将lambda表达式作为一个函数模版的参数,该参数是一个可调用的对象,这种情况下的lambda表达式就可能需要传参或是返回值。如果需要给lambda函数传递参数,在lambda introducer后附上参数列表即可。

以下是一个具体的例子,通过lambda函数对vector中的元素依次打印输出

std::vector<int> data=make_data();
std::for_each(data.begin(),data.end(),[](int i){std::cout<<i<<"\n";});

返回值也很简单,如果lambda函数的函数体只包含一个return语句,那么返回值的类型就是lambda函数的返回值类型 。例如,使用lambda函数来等待std::condition_variable中的flag被设置(这里都用多线程的例子,等待函数未来会填坑(也许))。

std::condition_variable cond;
bool data_ready;
std::mutex m;
void wait_for_data(){
    std::unique_lock<std::mutex> lk(m);
    cond.wait(lk,[]{return data_ready;}); 
}

这里有全局变量conddata_readym 。传递给cond.wait的lambda表达式的返回值从data_ready 的类型推断,因此返回值是布尔类型。每当条件变量从等待中苏醒时,他就会调用lambda表达式与mutex锁,并且对于wait()的调用只有data_ready为真时才会返回(挖个坑)。

如果lambda函数的函数体包含多个return语句,那此时就必须显式地指定返回类型(当然单个return语句也可以指定)。返回值类型由参数列表()后接着的->指定。这也意味着,即使lambda函数不需要传参,若要显示指定返回值类型,也要写(空)参数列表()。 则上述的例子可以写为:

cond.wait(lk,[]()->bool{return data_ready;});

通过指定返回类型,可以展开lambda以打印log或执行一些更复杂的逻辑:

cond.wait(lk,[]()->bool{
    if(data_ready){
        std::cout<<”Data ready”<<std::endl;
        return true;
    }else{
        std::cout<<”Data not ready, resuming wait”<<std::endl;
        return false;
    }
});

虽然像这样简单的lambda函数已经可以大大简化代码,但它的真正威力到它们捕获局部变量时才会真正显现出来。

引用局部变量的lambda函数

使用空lambda inrocuder[]的函数不能引用任何的局部变量,其只能使用全局变量或者通过函数传参。如果想要访问全局变量,就需要捕获(capture)它。捕获全局变量最简单的方式就是通过[=]将作用域中所有的变量全部捕获,则lambda函数在创建时就会拷贝局部变量的一份副本以供访问。考虑以下代码:

std::function<int(int)> make_offseter(int offset){
    return [=](int j){return offset+j;};
}

每次对make_offseter的调用都会通过std::function<>函数包装返回一个新的lambda函数体,该函数添加了对于参数的偏移。

int main()
{
    std::function<int(int)> offset_42=make_offseter(42);
    std::function<int(int)> offset_123=make_offseter(123);
    std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
    std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
}
/*
输出为
54,135
54,135
*/

这是最安全的局部变量捕获形式:拷贝所有的局部变量,因此可以返回一个lambda函数并在原来函数的范围之外调用它。

但这并不是唯一的方法;我们也可以选择通过引用来捕获所有局部变量。在这种情况下,一旦其引用的变量由于退出所属的函数或块作用域而销毁时,调用lambda函数就是未定义行为,就像引用在其他情况下已经被销毁的变量一样。以下介绍关于[&] 对局部变量的引用。

int main() {
    int offset = 42; // 1
    std::function<int(int)> offset_a = [&](int j) { return offset + j; }; // 2
    offset = 123; // 3
    std::function<int(int)> offset_b = [&](int j) { return offset + j; }; // 4
    std::cout << offset_a(12) << "," << offset_b(12) << std::endl; // 5
    offset = 99; // 6
    std::cout << offset_a(12) << "," << offset_b(12) << std::endl; // 7
}
/*
输出为
135,135
111,111
*/

上述两次打印的结果不同,这就是引用所带来的。当然,C++的语法支持你可以在同一个lambda表达式中同时使用拷贝与引用的特性,通过调整[]中的参数来选择特定的类型进行局部变量的捕获。可以比较下面三个例子的区别。

  • 如果想要拷贝大部分变量,而部分引用,可以在=后加上部分参数的引用[=, &j, &k]
int main() {
    int i=1234,j=5678,k=9;
    std::function<int()> f=[=,&j,&k]{return i+j+k;};
    i=1;
    j=2;
    k=3;
    std::cout<<f()<<std::endl;
}
/*
输出为
1239
*/
  • 相反的,如果想要引用大部分变量而部分拷贝,只需在&后加上部分拷贝参数[&, i, j]
int main(){
    int i=1234,j=5678,k=9;
    std::function<int()> f=[&,j,k]{return i+j+k;};
    i=1;
    j=2;
    k=3;
    std::cout<<f()<<std::endl;
}
/*
输出为
5688
*/
  • 当然,如果不用=或者&,则可以实现部分局部变量的捕获。
int main() {
    int i=1234,j=5678,k=9;
    std::function<int()> f=[&i,j,&k]{return i+j+k;};
    i=1;
    j=2;
    k=3;
    std::cout<<f()<<std::endl;
}
/*
输出为
5682
*/

最后一种方式中,如果lambda函数体中出现了没有被捕获的变量就会引起编译错误,因此当选择这种方式时,如果包含lambda的函数是类的成员函数,那么需要对类成员变量的访问特别注意。成员变量无法直接捕获。如果在函数中需要捕获类的成员变量,则需要捕获this指针。下面的例子展示了这种情况:通过捕获this指针访问类的成员变量some_data

struct X {
    int some_data;

    void foo(std::vector<int> &vec) {
        std::for_each(vec.begin(), vec.end(),
                      [this](int &i) { i += some_data; });
    }
};

lambda函数在并发的上下文中很有用,可以作为std::condition_variable::wait() std::packaged_task<> 的谓词,也可以作为 线程池中线程函数std::thread的构造函数,或者是作为并行算法的实现在parallel_for_each() 中被使用。

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值