您的位置:首页 > 编程语言 > C语言/C++

C++11 lambda使用注意

2016-07-17 22:58 176 查看
    c++11开始支持了lambda,在很多地方提供了很大地便利,尤其是在STL中。但在真正弄清楚之前,不要滥用。lambda虽然方便,好用,但也有一些需要注意的地方,使用不当会造成一些奇奇怪怪的错误。其实C++11都是这样,提供了更方便的库,但是都有一些坑,不去了解就大量使用很容易掉坑。

    1.lambda对象捕获

    通过lambda的捕获列表来指定需要捕获的对象及捕获方式。能够被捕获的对象范围和lambda所在块的访问范围是一样的,值得注意的是函数内的静态对象和非静态类成员函数中的this指针。

    2.lambda可能的实现方式

    编译器通过为每一个lambda表达式生成一个闭包类实现。捕获的对象作为类成员数据,定义一个operator()重载,执行体就是lambda中的执行体,并且用const修饰,也就是你不能修改成员数据。如果对象设置的捕获方式指定为传值,成员对象则声明为被捕获对象的类型并不做修饰,如果指定为引用,则成员对象声明为被捕获对象类型的指针并用mutable修饰,这样你就能在const修饰的执行体中修改引用的对象。

    每一个lambda表达式都会生成一个独立的类,即使语句是完全一样的。用编译器查看lambda表达式的型别却并不是一个类,而是和普通C函数一样,是个函数类型,也就是    ret(args...)

    3.值得注意的捕获行为

    lambda捕获列表中可以指定 [=] 或 [&] 默认传值或引用的方式捕获能捕获的对象,也可以指定 [=someobj, &someotherobj] 分别指定各个对象的捕获方式。一般情况下表现都会如你预期,但有几个值得注意的地方:野指针问题、类成员对象的捕获和this指针、函数体内局部的静态对象。

    如果采用引用的方式捕获,如2中说明,是用指针方式来引用。在很多情况下,lambda并不会立即执行,而是传递给其他线程执行或在将来某个时候执行。这样如果你引用的对象在lambda执行前就被摧毁了,就产生了问题。但有时程序还能按照你的想法运行,那其实是碰巧没有其他操作覆盖那片内存区域,而且你没有实现自己的析构函数而已。

    类成员对象其实是通过向非静态类成员函数传递默认的this指针来实现访问的。因此如果在非静态类成员函数中将lambda捕获列表设置为 [ ] 其实是无法捕获this指针和类成员对象的,如果在lambda中使用类成员对象将会产生编译错误。

    还要注意函数类的局部静态对象捕获是采用的引用方式捕获的,即使你使用了 [=] 来指定按值的方式捕获。

    4.需要在lambda内部维护一些状态时,请慎重考虑

    有时为了实现一些特殊需求,你可能会想到在lambda中维护自己的一些状态,就像之前在STL的函数对象中维护自己的状态一样。通过mutable修饰lambda表达式,使用值方式捕获对象,这样就将捕获的对象变成了lambda内部的状态对象。

    如果这样的lambda只使用一次,或则每次使用都通过引用则不会产生问题。如果使用中有多次拷贝,则可能产生一些意想不到的问题。举个例子:

int count=0;
std::vector<int> nums{1,2,3,4,5,6,7,8,9,0};
auto _pos = std::remove_if(nums.begin(), nums.end(), <pre class="cpp" name="code">            [=count] (int) mutable
{
return ++count == 3;
});
nums.erase(_pos, nums.end());
for(auto val : nums)
std::cout <<val <<' ';


看起来代码功能是实现将vector中第三个元素删掉,但执行结果却是输出 1 2 4 5 7 8 9 0,第3个和第6个元素都被删掉了!原因要从std::remove_if中查找,查看remove_if的实现

template < typename ForwIter, typename Predicate>
ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
{
beg = find_if(beg, end, op);                   //注意这里有一次拷贝
if(beg == end)
return beg;
ForwIter next = beg;
return remove_copy_if(++next, end, beg, op);   //这里又有一次拷贝
}

根本原因是在两个地方都对 predicate(即指定的lambda对象)进行了一次拷贝,而不是引用。由此导致两次的累加都从0开始,最终有两个元素满足了条件被删除。在c++的STL中,只有remove_if 会有这个问题。不过在需要使用自己维护的状态时最好要小心,尽量采用其他方法绕过。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c11 lambda