Effective Modern C++ 条款31 对于lambda表达式,避免使用默认捕获模式

对于lambda表达式,避免使用默认捕获模式

个人看法 英文原著中使用的是“avoid default capture modes”,所以我在文中翻译为“避免使用默认捕获模式”,但是我认为把“默认捕获模式”称为“隐式捕获模式”更好,因为作者所指的“默认捕获模式”是指在捕获语句中只出现等号或者引用符号(即“[=]”或“[&]”),而不出现捕获的变量名,但为了符合英文,还是把“default capture”翻译为“默认捕获”。

C++11中有两种默认捕获模式:引用捕获或值捕获。默认的引用捕获模式可能会导致悬挂引用,默认的值捕获模式诱骗你——让你认为你可以免疫刚说的问题(事实上没有免疫),然后它又骗你——让你认为你的闭包是独立的(事实上它们可能不是独立的)。

那就是本条款的总纲。如果你是工程师,你会想要更具体的内容,所以让我们从默认捕获模式的危害开始说起吧。

引用捕获会导致闭包包含一个局部变量的引用或者一个形参的引用(在定义lamda的作用域)。如果一个由lambda创建的闭包的生命期超过了局部变量或者形参的生命期,那么闭包的引用将会空悬。例如,我们有一个容器,它的元素是过滤函数,这种过滤函数接受一个int,返回bool表示传入的值是否可以满足过滤条件:

using FilterContainer =                       // 关于using,看条款9
    std::vector<std::function<bool(int)>>;    // 关于std::function,看条款2

FilterContainer filters;               // 含有过滤函数的容器 

我们可以通过添加一个过滤器,过滤掉5的倍数,像这样:

filters.emplace_back(         // 关于emplace_back, 看条款42
  [](int value) { return value % 5 == 0; }
);

但是,我们可能需要在运行期间计算被除数,而不是直接把硬编码5写到lambda中,所以添加过滤器的代码可能是这样的:

void addDivisorFilter()
{
    auto calc1 = computeSomeValue1();
    auto calc2 = computeSomeValue2();

    auto divisor = computeDivisor(calc1, calc2);

    filters.emplace_back(
      [&](int value) { return value % divisor == 0; }   // 危险!对divisor的引用会空悬
    );
}

这代码有个定时炸弹。lambda引用了局部变量divisor, 但是局部变量的生命期在addDivisorFilter返回时终止,也就是在filters.emplace_back返回之后,所以添加到容器的函数本质上就像是一到达容器就死亡了。使用那个过滤器会产生未定义行为,这实际上是在创建过滤器的时候就决定好的了。

现在呢,如果显式引用捕获divisor,会存在着同样的问题:

filters.emplace(
  [&divisor](int value)          // 危险!对divisor的引用依然会空悬
  { return value % divisor == 0; }
);

不过使用显示捕获,很容易就可以看出lambda的活性依赖于divisor的生命期。而且,写出名字“divisor”会提醒我们,要确保divisor的生命期至少和lambda闭包一样长。比起用

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lambda表达式C++11引入的一种函数对象,可以在需要函数对象的地方使用,比如作为函数参数、返回值等。Lambda表达式的语法形式如下: ``` [capture list](parameters) mutable exception -> return type { // function body } ``` 其中,`capture list` 表示捕获列表,用于捕获外部变量。`parameters` 表示函数参数列表,`mutable` 用于表示是否可以修改值传递的变量,`exception` 是异常列表,`return type` 表示返回类型,`function body` 则是函数体。 在Lambda表达式中,可以通过 `[this]` 捕获当前对象的指针,即 `this` 指针,可以方便地访问当前对象的成员变量和成员函数。例如: ``` class MyClass { public: void foo() { int x = 1; auto lambda = [this, x]() mutable { this->m_member_var += x; this->m_member_function(); x++; }; lambda(); } private: int m_member_var; void m_member_function(); }; ``` 在上面的例子中,Lambda表达式通过 `[this, x]` 捕获当前对象的指针和 `foo()` 函数中定义的变量 `x`。在 Lambda 表达式中可以通过 `this->m_member_var` 和 `this->m_member_function()` 访问当前对象的成员变量和成员函数。由于 `x` 是值传递的,所以在 Lambda 表达式中需要使用 `mutable` 关键字使其可修改,可以通过 `x++` 修改变量的值。最后调用 `lambda()` 执行 Lambda 表达式。 需要注意的是,Lambda表达式捕获 `this` 指针时,需要保证当前对象是有效的,即不能在已经销毁的对象中访问成员变量和成员函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值