Lambda拾遗 Lambda的状态 一般来说, 我们会说当需要保持状态时, 我们会使用函数对象来实现, 这样写很直观. 但是同样可以使用lambda实现某些特别的效果. 例如下面的代码:
1 2 3 4 5 6 7 8 9 10 void NormalTester::test_case2 () { int x = 1 ; auto fun1 = [x]()mutable { TRACE () << "执行结果: " << ++x; }; TRACE () << "执行前: " << TSHOW (x); fun1 (); fun1 (); fun1 (); TRACE () << "执行后: " << TSHOW (x); }
注意, 这里必须要在fun1
定义为mutable
, 否则编译会不过. 输出为:
1 2 3 4 5 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2 (...)] fun1执行前: x:= 1 , QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_1>::operator () (...)] fun1执行结果: 2 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_1>::operator () (...)] fun1执行结果: 3 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_1>::operator () (...)] fun1执行结果: 4 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2 (...)] fun1执行后: x:= 1 ,
可以看到, 每次调用fun1
的时候x
都被加1了, 但是外部的x
并没有被修改. 说明lambda修改了x
的一个副本, 并且在每次调用间保持了其状态.
道理还是很简单地. 它大概会被转换成下面的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int x = 1 ;struct fun1class { fun1class (int x) : _x{x}{} void operator () mutable { TRACE () << "执行结果: " << ++_x; } }fun1 (x); fun1 ();fun1 ();fun1 ();...
这里有两点:
默认情况下, lambda是被const
所修饰的, 所以值捕获的变量不能被修改–因为它被实现为函数对象的属性了. 如果我们希望能修改, 就要显式将其设置为mutable
的.
同一个lambda的实例的每次被调用, 它们的状态是可以被保存的.
于是, 如果我们这样写, 会是什么样子呢?
1 2 3 4 5 6 7 8 ... x = 1 ; TRACE () << "fun3执行前: " << TSHOW (x);for (int i=0 ; i<3 ; ++i){ [x]() mutable {TRACE () << "fun1执行结果: " << ++x;}(); } TRACE () << "fun3执行后: " << TSHOW (x);
是的, 是这样:
1 2 3 4 5 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2 (...)] fun3执行前: x:= 1 , QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_3>::operator () (...)] fun1执行结果: 2 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_3>::operator () (...)] fun1执行结果: 2 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2::<lambda_3>::operator () (...)] fun1执行结果: 2 QDEBUG : NormalTester::test_case2() [ NormalTester::test_case2 (...)] fun3执行后: x:= 1 ,
这样做每次都是新的实例, 和上一次隔离了.
当然, 如果我们用引用捕获, 则是另一种情况, 这种太常见, 就不写了.
Lambda类型 关于Lambda, 有两点比较有意思的:
每个定义的lambda都有自己独一无二的类型. 即使两个lambda的参数, 返回值, 甚至实现完全相同, 其类别也是不一样的.
Lambda可以保存到std::function
中, 只要两个std::function
的参数和返回值类型相同, 不管它们是否捕获参数, 都认为是相同的.
比如下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 void NormalTester::test_case3 () { int val = 10 ; auto fun1 = [](int x){ qDebug () << x ;}; auto fun2 = [](int x){ qDebug () << x ; }; std::function<void (int )> fun10 = [](int x){qDebug () << x ;}; std::function<void (int )> fun11 = [v=val](int x){ qDebug () << ++x << v; }; qDebug () << "\n" << TSHOW ((std::is_same_v<decltype (fun1), decltype (fun2)>)) << "\n" << TSHOW ((std::is_same_v<decltype (fun10), decltype (fun11)>)) << Qt::endl; }
其输出是:
1 2 (std::is_same_v<decltype(fun1), decltype(fun2)>):= false , (std::is_same_v<decltype(fun10), decltype(fun11)>):= true ,
在上面的例子中, 定义完全相同的fun1
和fun2
是不同的类型, 而差别更大的func10
和func11
却是相同类型的. 这样, std::function
的一个很容易想到的用途就是作为指代lambda的参数类型. 但是, 使用std::function
会有一定的性能损失. 这种场景下, 如果没有别的限制, 通常使用泛型将是更高效更普适的做法.
另外, 我们还可以直接将没有捕获块的lambda当作函数指针传递给以C方式声明的函数指针形参. 例如:
1 2 3 4 5 6 7 8 9 10 11 extern "C" void loginfo (const QString& msg, void (*callback)(const QString&)) { callback (msg); } void NormalTester::test_case4 () { auto logfunc = [](const QString& msg){ qDebug () << msg; }; loginfo ("Hellow" , logfunc); }
我还在别处见过另一个新奇的说法, 说是这种情况下, 需要在lamba定义的前面显式加一个+
才行, 不过我当前使用的编译器(MSVC2022, C++23标准)并不需要. 有可能是老版本C++编译器或标准的限制. 如果你的代码编译失败, 可以尝试修改一下试试:
1 auto logfunc = +[](const QString& msg){ qDebug () << msg; };
lambda的泛型 在C++17中引入了lambda的泛型. 当然其语法和传统的泛型函数有点差别. 它使用的是auto
. 这个没啥特别的, 去看一下C++17的规范就可以了.
然后, 在C++20中, 又对lambda的泛型做了增强, 也支持传统函数的泛型写法了. 引入这种写法, 会给元编程带来不少便利.
1 2 3 4 5 6 7 8 void NormalTester::test_case5 () { auto func1 = [](auto v){ return ++v;}; auto func2 = []<typename T>(T v){ return ++v; }; qDebug () << TSHOW (func1 (12 )); qDebug () << TSHOW (func2 (12 )); }
上面的函数, 只有指定c++20
或c++latest
才能编译通过. 如果我们强制使用C++17, MSVC编译器对func2
这行会这样报错:
1 2 D:\IveiLib\QtInPractice\source\ScanerSuit\UnitTest\UnitTest_NormalTester\tst_normaltester.cpp:135: error: C7563: 要使用模板参数列表创建 lambda,需要至少“/std:c++20” D:\IveiLib\QtInPractice\source\ScanerSuit\UnitTest\UnitTest_NormalTester\tst_normaltester.cpp:135: warning: Explicit template parameter list for lambdas is a C++20 extension