c++的lambda使用注意事项,可能导致的崩溃问题分析

简介: c++的lambda使用注意事项,可能导致的崩溃问题分析

Lambda表达式是现代C++的一个语法糖,挺好用的。但是如果使用不当,会导致内存泄露或潜在的崩溃问题。这里总结下Lambda表达式的使用注意事项,避免在使用中的一些陷阱。


Lambda介绍


“Lambda表达式是现代C++在C ++ 11和更高版本中的一个新的语法糖 ,在C++11、C++14、C++17和C++20中Lambda表达的内容还在不断更新。 lambda表达式(也称为lambda函数)是在调用或作为函数参数传递的位置处定义匿名函数对象的便捷方法。通常,lambda用于封装传递给算法或异步方法的几行代码 。


崩溃举例


请看以下示例,会导致崩溃吗?


示例一:

// 示例一
void MainWindow::on_cb_1_currentIndexChanged(const QString &arg1)
{
    qDebug() << "on_cb_1_currentIndexChanged:"<<arg1;
    QFuture<void> future = QtConcurrent::run([&](){
        QList<QHash<QString,QString>> data;
        QString where = QString("Input_date = '%1'").arg(arg1);
        qDebug() <<"where:"<<where;
    });
}


示例二:


// 示例二
void MainWindow::on_cb_1_clicked()
{
    ui->tb->append("on_cb_1_clicked");
    QFuture<void> future = QtConcurrent::run([&](){
        QList<QHash<QString,QString>> data;
        db->getData("tb_block",data);
        qDebug() << "size:"<<data.size();
        if(data.size() > 0){
             qDebug() << "size:"<<data.size();
             QMetaObject::invokeMethod(qApp, [&]{
                  ui->cb_1->clear();
                 for(auto &lt:data){
                    ui->cb_1->addItem(lt.value("Input_Date"));
                 }
             });
        }
    });
}


示例三:


// 示例三
using FilterContainer = std::vector<std::function<bool(int)>>;  
FilterContainer filters;   // 含有过滤函数的容器
void addDivisorFilter()
{
    auto divisor = 5;
    filters.emplace_back(
      [&](int value) { return value % divisor == 0; }   // 危险!对divisor的引用会空悬
    );
}


崩溃原因分析


先说结论吧,以上三个示例均会导致崩溃。崩溃原因分析:


示例一,崩溃在QtConcurrent::run开启的线程里访问了arg1。


这个Lambda表达式写法中,使用的是引用捕获[&],当事件处理on_cb_1_currentIndexChanged结束后,在线程里还引用使用了arg1这个参数,而这个agr1的引用已经失效了,这时候线程里还去使用它,导致了崩溃。


示例二,崩溃原因同示例一。局部变量data,尽管QList容器空间是在堆上分配的,但data这个变量分配在栈上。在QMetaObject::invokeMethod开启的Lambda表达式中,同样是使用的[&],引用捕获。当临时变量data失效时,在invokeMethod中仍使用了这个变量data的引用(悬空引用问题),导致了崩溃。


示例三,lambda引用了局部变量divisor, 但是局部变量的生命期在addDivisorFilter返回时终止,也就是在filters.emplace_back返回之后,所以添加到容器的函数本质上就像是一到达容器就死亡了,使用那个过滤器会产生未定义行为。


以上示例崩溃的原因都可以归结为使用了悬空引用。需要特别注意悬空引用。


悬空引用


引用捕获会导致闭包包含一个局部变量的引用或者一个形参的引用(在定义lamda的作用域)。如果一个由lambda创建的闭包的生命期超过了局部变量或者形参的生命期,那么闭包的引用将会空悬。


正确写法


正确的写法如下:


需要把arg1和data以值传递的方式捕获进来。


// 示例一
void MainWindow::on_cb_1_currentIndexChanged(const QString &arg1)
{
    qDebug() << "on_cb_1_currentIndexChanged:"<<arg1;
    QFuture<void> future = QtConcurrent::run([&,arg1](){
        QList<QHash<QString,QString>> data;
        QString where = QString("Input_date = '%1'").arg(arg1);
        qDebug() <<"where:"<<where;
    });
}
// 示例二
void MainWindow::on_cb_1_clicked()
{
    ui->tb->append("on_cb_1_clicked");
    QFuture<void> future = QtConcurrent::run([&](){
        QList<QHash<QString,QString>> data;
        db->getData("tb_block",data);
        qDebug() << "size:"<<data.size();
        if(data.size() > 0){
             qDebug() << "size:"<<data.size();
             QMetaObject::invokeMethod(qApp, [&,data]{
                  ui->cb_1->clear();
                 for(auto &lt:data){
                    ui->cb_1->addItem(lt.value("Input_Date"));
                 }
             });
        }
    });
}
// 示例三
using FilterContainer = std::vector<std::function<bool(int)>>;  
FilterContainer filters;   // 含有过滤函数的容器
void addDivisorFilter()
{
    auto divisor = 5;
    filters.emplace_back(
      [&,divisor](int value) { return value % divisor == 0; } 
    );
}


注意事项


使用Lambda表达式的一些注意事项:


1、使用到外部引用要小心谨慎,避免悬空引用。


若需要用到的外部局部变量,需以值传递的方式捕获而非引用捕获(若是外部指针变量则需深拷贝)。


2、谨慎使用或者不用外部指针。

如果你用值捕获了个指针,你在lambda创建的闭包中持有这个指针的拷贝,但你不能阻止lambda外面的代码删除指针指向的内容,从而导致你拷贝的指针空悬。


3、注意引用捕获陷阱:引用捕获[&]不要使用外部局部变量。


4、注意this陷阱:lambda里避免有全局变量或静态变量或者比当前类生命周期更长的变量。Effective Modern C++ 条款31 对于lambda表达式,避免使用默认捕获模式。


5、避免使用默认捕获模式((即“[=]”或“[&]”,它可能导致你看不出悬空引用问题)。


默认值捕获就意外地捕获了this指针,而不是你以为的外部变量。


在C++14中,捕获成员变量一种更好的方法是使用广义lambda捕获(generalized lambda capture,即,捕获语句可以是表达式[x= x],条款32)。


6、注意捕获的是可见(在创建lambda的作用域可见)的非static局部变量(包含形参)。


每一个非static成员函数都有一个this指针,然后每当你使用类的成员变量时都用到这个指针。这时候lambda闭包的活性与Widget对象的生命期有紧密关系,闭包内含有Widget的this指针的拷贝。


正常情况下,lambda表达式中访问类的对象成员变量需要捕获this,但是这里捕获的是this指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题。


好在C++17增加了新特性可以捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关,使用上就安全一些。


引用


C++ Lambda表达式详解_lucky-wz的博客-CSDN博客_c++ lambda表达式


C++笔记-lambda表达式需要注意的地方_IT1995的博客-CSDN博客


浅谈c++中的Lambda表达式_网格小生的博客-CSDN博客


C++ lambda表达式_悲伤土豆拌饭的博客-CSDN博客_c++ lambda 表达式


C++11:lambda表达式的陷阱_zzhongcy的博客-CSDN博客_c++ lambda 异常


关于 c++ lambda 函数需要注意的点_风竹夜的博客-CSDN博客


C++ 从Lambda的使用到对C++闭包语法的理解/Lambda的坑_WhiteTian的博客-CSDN博客_c++闭包


lambda表达式使用及缺陷分析展示_谢白羽的博客-CSDN博客_c++ lambda表达式优缺点


c++14新特性_C++17新特性_杨佶Kulbear的博客-CSDN博客


c++11~c++20 -07-使用lambda注意点_发如雪-ty的博客-CSDN博客 

相关文章
|
8天前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
35 3
|
7月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
105 5
|
3月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
17 0
|
5月前
|
机器学习/深度学习 算法 C++
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
|
5月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
65 1
|
6月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
56 13
|
6月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
7月前
|
算法 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(中)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
65 2
|
7月前
|
算法 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(上)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
43 1
|
6月前
|
设计模式 算法 程序员
【C++】大气、正规的编程习惯:C++学习路径与注意事项
【C++】大气、正规的编程习惯:C++学习路径与注意事项
68 0