C++11 lambda表达式

缘起

最近在读boost.asio的实例代码,其中有使用c++11标准的lambda表达式。都有些看不懂代码的意思。异步调用注册回调函数时,直接使用lambda表达式来制作。在C语言的libEvent通讯库,当一个数据包达到,一个连接建立这样的回调,都是通过函数指针来制作。ACE框架是通过接口类,继承对象之后编写回调函数来做。而在之前boost.asio版本的用法中,基本上都是使用的boost.bind,boost.function库模拟了libevent的函数指针方式。有了匿名函数之后可以更加节约代码,直接在调用的时候,将回调函数写完。这些写法在java、js、c#语言里面很早就有这样的支持。下面是js中通过http模块请求数据的实例:

var http = require('http'), 
  urls = ['www.baidu.com','www.10jqka.com.cn','www.duokan.com']; 
function fetchPage(url){ 
  var start = new Date(); 
  http.get({host:url},function(res){ 
    console.log("Got response from:" + url); 
    console.log("Request took:",new Date() - start, "ms"); 
  }); 
} 
for(var i=0; i<urls.length; i++){ 
  fetchPage(urls[i]); 
} 

匿名函数好处

1.lambda表达式是C++里面的闭包,他也有访问自己域上面的变量的能力。闭包可以理解成“函数绑定了一堆变量”;而相对类比的是类:“类是成员变量绑定了一堆函数”。
2.写起来将会更加灵活小巧,比如说,lambda表达式作为一个函数指针,能被传递给框架代码做一些特殊处理,而且在处理的过程中,我们可以很方便的使用到调用时候的上下文变量,真是左右逢源。(如果使用libEvent写类似代码,可能在设计的开始就要确定好自定义的struct。在注册事件时将数据填充好并且传递给libEvent,当libEvent有事件到达的时候,他将会把这个struct指针传回来。)

代码差异

下面我们来通过两段代码来对比一下c++11标准、c++标准boost.bind方式的代码,在编写boost.asio的accept回调写法的差异:
c++ 11 标准:

void do_accept()
    {
        acceptor_.async_accept(socket_,
            [this](boost::system::error_code ec)
        {
            if (!ec)
            {
                std::make_shared<session>(std::move(socket_))->start();
            }

            do_accept();
        });
    }

c++ 标准:

  void start_accept()
  {
    session* new_session = new session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }
  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }
    else
    {
      delete new_session;
    }

    start_accept();
  }

两段代码达到了相同的效果。

cppreference_Lambda_表达式

lambda学习笔记:

构造一个闭包:一个无名字函数对象能访问范围中的变量。注意这个lambda表达式是一个闭包。它能访问到自己函数域外部的变量。但是需要通过[capture-list]里面定义lambda表达式的访问方式。

语法

1.[ capture-list ] ( params ) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body }
2.[ capture-list ] ( params ) -> ret { body }
3.[ capture-list ] ( params ) { body }
4.[ capture-list ] { body }

1.第一个是全定义模式;
2.申明一个常量lambda:被captured的对象是复制的不能被修改;
3.简略返回值模式:闭包返回值类型依据下面两条规则:
3.1.如果这个body没有任何内容,只有依据return (表达式);时,返回类型就依据返回的类型;(until C++14)
否则将返回void
3.2.返回类型将来自于 return statements随返回类型申明的推导函数。
4.省略掉参数表模式;

解释

mutable

允许body去修改通过值捕获参数内容。

#include <iostream>

int main()
{
    int a = 1, b = 1, c = 1;

    auto m1 = [a, &b, &c]() mutable {
        auto m2 = [a, b, &c]() mutable {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };

    a = 2; b = 2; c = 2;

    m1();                             // calls m2() and prints 123
    std::cout << a << b << c << '\n'; // prints 234
}

如果将mutable去掉

    auto m1 = [a, &b, &c]()  {
        auto m2 = [a, b, &c]()  {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };

将会直接出现这些报错:

1>f:\learn\c++\test_boost\main.cpp(94): error C3491: “a”: 无法在非可变 lambda 中修改通过复制捕获
1>f:\learn\c++\test_boost\main.cpp(94): error C3491: “b”: 无法在非可变 lambda 中修改通过复制捕获
1>f:\learn\c++\test_boost\main.cpp(96): error C3491: “a”: 无法在非可变 lambda 中修改通过复制捕获

在capture-list理解完之后将会更加加深映像。其实定义了mutable之后,将会默认直接把外部的变量都做一个copy的副本来做修改。

exception

抛出异常选项

attributes

lambda属性

capture-list

0~n由,分割符分割的抓取列表,capture-list的开头是使用一个capture-default。下面列举了capture-default的一些实例:

  • [a,&b] 定义a变量传值方式使用,b变量是引用方式使用;
  • [this] 将this指针传值方式加以使用
  • [&,x] x 显示地以传值方式加以使用。其余变量以引用方式加以使用。
  • [=,&z] z 显示地以引用方式加以使用。其余变量以传值方式加以使用。
  • [] 不抓取

读到这里我capture其实是在定义闭包访问scope中变量的方式。不像Lua闭包一样的,在闭包里面如果要访问scope中的变量的时候,需要在这个里面定义访问的规则。

看完这个部分之后大概能看懂了这个代码了:

    void do_read()
    {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length)
        {
            if (!ec)
            {
                do_write(length);
            }
        });
    }

[this,self]这个的意思是将this和self这个share_ptr指针,以传值方式传递给闭包来使用。

params

就是匿名函数的参数表。

ret

定义lambda表达式的返回值

body

函数实现。

总结:

使用lambda表达式来优化程序还是很有必要。特别是在异步处理的时候,closure在写异步调用能更加符合人的思维方式。

### C++11 Lambda表达式使用教程 #### 定义与基本语法 C++11引入了lambda表达式这一强大特性,使得开发者能够更加便捷地定义和创建匿名函数。这种新的功能不仅简化了代码编写过程,还提升了程序的功能性和执行效率[^1]。 Lambda表达式的通用形式如下: ```cpp [capture](parameters)->return_type { body } ``` 其中`capture`表示捕获列表,用于指定如何访问外部作用域中的变量;`parameters`代表参数列表;`->return_type`可选部分用来显式声明返回类型;最后是函数体`body`[^2]。 #### 捕获列表详解 捕获列表允许lambda表达式获取其所在环境内的局部变量或全局变量。常见的几种方式包括但不限于按值传递(`[var]`)、按引用传递(` [&var] `),以及默认全部按值/引用捕获(` [= ] / [&] `)[^3]。 ##### 示例:按值捕获 当采用按值的方式捕获时,意味着会复制一份原始数据供内部逻辑操作而不影响原值。 ```cpp #include <iostream> using namespace std; int main(){ int value = 42; auto func_by_value = [value]() { cout << "Value captured by copy: " << value << endl; }; ++value; // 修改外界的value func_by_value(); // 输出的是未修改前的数值 } ``` ##### 示例:按引用捕获 相反地,如果选择了按引用的形式,则任何对于被捕获变量的操作都会直接影响到实际存在的那个实例。 ```cpp #include <iostream> using namespace std; int main(){ int ref_val = 88; auto modify_ref = [&ref_val]() mutable{ ++ref_val; cout << "Modified reference-captured variable to : " << ref_val << endl; }; modify_ref(); } ``` #### 实际应用场景举例 考虑这样一个场景——我们需要交换两个整型数的位置而无需借助额外的空间开销。此时可以巧妙运用带有适当捕获机制的lambda表达式来完成任务[^5]。 ```cpp void swapTwoNumbers(int &num1, int &num2){ auto swapper = [&](int &a, int &b){ int temp=a;a=b;b=temp; }; swapper(num1,num2); } // 或者更为简洁的做法直接在lambda体内处理 auto direct_swap=[&](int &x,int &y){swap(x,y);} direct_swap(a,b); ``` #### 关于赋值行为的重要注意事项 值得注意的一点在于,尽管可以通过拷贝构造的方式来生成另一个具有相同行为的新对象,但是不同lambda表达式之间不允许互相赋值,即便它们看起来拥有完全一致的结构和语义[^4]。 ```cpp auto hello_world_1=[](){std::cout<<"Hello World"<<std::endl;}; auto hello_world_2=[](){std::cout<<"Hello World"<<std::endl;}; // 下面这行会导致编译错误 //hello_world_1=hello_world_2; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值