lambda 表达式和右值引用是 C++11 的两个非常有用的特性。
lambda 表达式实际上会由编译器创建一个 std::function
对象,以值的方式捕获的变量则会由编译器复制一份,在 std::function
对象中创建一个对应的类型相同的 const 成员变量,如下面的这段代码:
这段代码的输出如下:
由上面调用 funca
时的输出,可以看到 lambda 表达式以值的方式捕获的对象 str,其地址在 lambda 表达式内部和外部是不同的。
std::function
类对象和普通的魔板类对象一样,可以拷贝构造,如:
由调用 funcb
时的输出,可以看到拷贝构造时是做了逐成员的拷贝构造。
std::function
类对象可以赋值,如:
由调用 funcc
时的输出,可以看到赋值时是做了逐成员的赋值。
std::function
类对象可以移动构造,如:
由移动构造之后,调用 funca
和 funcd
时的输出,可以看到移动构造时是做了逐成员的移动构造。
std::function
类对象可以移动赋值,如:
这里把移动赋值之后对 funcb
的调用注释掉了,这是因为,作为源的 funcb
在移动赋值之后被调用是,会抛出异常,如:
同时,由调用 funce
时的输出可以看到,该输出与 funcb
在移动赋值之前被调用时的输出完全相同。即移动赋值是将对象整体 move 走了,这与移动构造时的行为不太一样。
std::function
类对象的拷贝构造或者赋值,也需要满足类型匹配原则,如:
这行代码会造成编译失败,编译错误信息如下:
在 lambda 中以值的方式捕获的右值对象,只是在 lambda 的 std::function 对象中做了一份被捕获的右值对象的拷贝,而原来的右值则没有任何改变。
接下来再来看一段示例代码:
上面这段代码在执行时,输出如下:
funca
函数接收右值引用作为参数,由 funca
函数内部及函数调用前后的输出可以看到,std::move()
本身什么都没做,单单调用 std::move()
并不会将原来的对象的内容移动到任何地方。std::move()
只是一个简单的强制类型转换,将左值转为右值引用。同时可以看到,用右值引用作为参数构造对象,也并没有对右值引用所引用的对象产生任何影响。
funcb
函数接收左值引用作为参数,上面的代码中,如下这一行注释掉了:
这是因为,funcb
不能用一个右值引用作为参数来调用。用右值引用作为参数,调用接收左值引用作为参数的函数 funcb
时,会编译失败:
不过,如果 funcb
接收 const 左值引用作为参数,如 void funcb(const std::string &str)
,则在调用该函数时,可以用右值引用作为参数,此时 funcb
的行为与 funca
基本相同。
funcc
函数接收左值作为参数,由 funcc
函数内部及函数调用前后的输出可以看到,由于有了左值作为接收者,传入的右值引用所引用的对象的值被 move 走,进入函数的参数栈对象中了。
funcd
函数与 funca
函数一样,接收右值引用作为参数,但 funcd
的特别之处在于,在函数内部,右值构造了一个新的对象,因而右值引用原来引用的对象的值被 move 走,进入了新构造的对象中。
再来看一段示例代码:
上面这段代码的输出如下:
在函数 foo()
中定义的 funa
及对 funa
的调用被注释掉了,这是因为这段代码会导致编译失败,具体的错误信息如下:
如我们前面提到的,在 lambda 表达式中,以值的方式捕获右值引用时,会在编译器为该 lambda 表达式生成的 std::function
类中生成一个 const 对象,const 对象是不能作为右值引用来调用接收右值引用为参数的函数的。
在函数 foo()
中定义的 funb
,相对于 funa
,在调用 bar()
时,为 str
裹上了 std::move()
。不过此时还是会编译失败。错误信息如下:
在 funb
中,str
是个 const 对象,因而还是不行。
在函数 foo()
中定义的 func
,相对于 funa
,加了 mutable
修饰。此时还是会编译失败。错误信息如下:
无法将左值绑定到一个右值引用上。
在函数 foo()
中定义的 fund
,相对于 func
,在调用 bar()
时,为 str
裹上了 std::move()
。此时终于可以编译成功,可以 move const 的 str
。
在函数 foo()
中定义的 fune
,相对于 funb
,以引用的方式捕获了右值引用。在 fune
中调用 bar()
,就如同 foo()
直接调用 bar()
一样。
在函数 foo()
中调用接收一个右值引用作为参数的函数 bar_bar()
生成一个函数。在函数 bar_bar()
中用 lambda 定义的函数对象 funf
,以引用的方式捕获一个右值,并在 lambda 中访问改对象。该 lambda 作为 bar_bar()
函数生成的函数对象。foo()
中调用 bar_bar()
时传入函数栈上定义的临时对象 stra
,并将 bar_bar()
返回的函数对象作为返回值返回。在 main()
函数中用 funcg
接收 foo()
函数返回的函数对象,并调用 funcg
,此时会发生 crash 或能看到乱码。crash 或乱码是因为,在 funf 中,访问的 str
对象实际上是 foo()
函数中定义的栈上临时对象 stra
,foo()
函数调用结束之后,栈上的临时对象被释放,main()
函数中调用 funcg
实际在访问一个无效的对象,因而出现问题。
Done。