栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Item 31: Avoid default capture modes.

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Item 31: Avoid default capture modes.

Item 31: Avoid default capture modes.

Effective Modern C++ Item 31 的学习和解读。

C++11 lambda 表达式有两种默认捕获模式:传引用捕获和传值捕获。默认传引用捕获可能导致引用悬挂的问题。默认传值捕获其实也不能避免这个问题,并且你的 lambda 闭包也不是独立的。

先看默认传引用捕获导致引用悬挂的问题。看下面的代码片段:

using FilterContainer = std::vector>;
FilterContainer filters; // filtering funcs

void addDivisorFilter()
{
  auto calc1 = computeSomeValue1();
  auto calc2 = computeSomeValue2();
  auto divisor = computeDivisor(calc1, calc2);
  filters.emplace_back( 
    [&](int value) { return value % divisor == 0; } // danger! ref to divisor will dangle!
  );
}

filters 存放了 lamdba 闭包,闭包引用了 addDivisorFilter 作用域内的局部变量 divisor,当离开 addDivisorFilter 作用域后,局部变量 divisor 将被析构,若此时使用 filters 则导致引用悬挂。

使用显示的传引用捕获也有同样的问题:

filters.emplace_back(
  [&divisor](int value)             // danger! ref to
  { return value % divisor == 0; }  // divisor will
);                                  // still dangle!

不过,显示的传引用捕获可以提醒我们 lambda 表达式的生命周期依赖于 divisor 的生命周期,也可以提醒我们需要确保 divisor 的生命周期要长于 lambda 表达式的生命周期。

解决上述问题,可以通过默认传值捕获的方式解决:

filters.emplace.back( 
  [=](int value) { return value % divisor == 0; }
)

但是,默认传值捕获也不一定能够解决悬挂问题:例如你通过传值的方式捕获一个指针,也即你拷贝了一个指针给 lambda 闭包了,但是你无法阻止 lambda 闭包外指针被 delete,从而导致指针悬挂的问题。看下面的例子:

class Widget {
public:
  … // ctors, etc.
  void addFilter() const; // add an entry to filters
private:
  int divisor; // used in Widget's filter
};

void Widget::addFilter() const
{
  filters.emplace_back(
    [=](int value) { return value % divisor == 0; }
  );
}

上面代码貌似是安全的。因为你通过默认传值捕获方式,应该不会有悬挂的问题。但是,lambda 表达式只能捕获作用域内的非静态局部变量,而 divisor 是一个成员变量。那么上面的代码为什么可以编译通过呢?可以先看下面这段代码:

void Widget::addFilter() const
{
  filters.emplace_back(
    [divisor](int value) { return value % divisor == 0; }
  );
}

这段代码编译无法通过:

main.cpp: In member function 'void Widget::addFilter() const':
main.cpp:19:6: error: capture of non-variable 'Widget::divisor'
   19 |     [divisor](int value) { return value % divisor == 0; }

正如我们所设想的,lambda 表达式无法捕获非静态成员变量。前面默认传值捕获之所以能够通过编译,原因其实是这里的 lambda 表达式捕获的是 this 指针,也就是将 this 指针拷贝进了闭包。编译器在内部将 divisor 替换成了 this->divisor,等价如下:

void Widget::addFilter() const
{
  filters.emplace_back(
    [=](int value) { return value % this->divisor == 0; }
  );
}

这里实际捕获的不是 divisor,而是 this 指针。了解了上述真相后,就不难理解默认传值捕获也可能导致指针悬挂的问题了。

using FilterContainer = std::vector>;
FilterContainer filters;

void doSomeWork() {
  auto pw = std::make_unique();
  pw->addFilter();
}

filters 包含了 Widget 的 this 指针的拷贝。 doSomeWork 执行完成后,Widget 将被析构,导致 filters 包含了一个悬挂的指针。

上述问题可以通过使用一个局部变量拷贝成员变量来解决。如下:

void Widget::addFilter() const
{
  auto divisorCopy = divisor; // copy data member
  filters.emplace_back(
    [divisorCopy](int value) // capture the copy
    { return value % divisorCopy == 0; } // use the copy
  );
}

或者默认传值捕获也是一样:

void Widget::addFilter() const
{
  auto divisorCopy = divisor; // copy data member
  filters.emplace_back(
    [=](int value) // capture the copy
    { return value % divisorCopy == 0; } // use the copy
  );
}

c++14 的方式更加简洁:

void Widget::addFilter() const
{
  filters.emplace_back(               // C++14:
    [divisor = divisor](int value)    // copy divisor to closure
    { return value % divisor == 0; }  // use the copy
  );
}

lambda 表达式只能捕获非静态局部变量,对于 static 或者 global 变量,lambda 表达式不会捕获。所以这些变量发生改变会影响到 lambda 表达的行为。使用默认传值捕获,可能会让你以为你的 lambda 闭包是独立的,不依赖外部变量的变化,其实不然。

void addDivisorFilter()
{
  static auto calc1 = computeSomeValue1(); // now static
  static auto calc2 = computeSomeValue2(); // now static
  static auto divisor = // now static
    computeDivisor(calc1, calc2);
  filters.emplace_back(
    [=](int value)   // captures nothing!
    { return value % divisor == 0; } // refers to above static
  );
  ++divisor;   // modify divisor
}

上述代码可能会让你产生错觉:因为是使用默认传值捕获,lambda 闭包是将 divisor 拷贝进去的。但是 static 变量不会被 lambda 闭包捕获, divisor 的改变会影响到 lambda 表达式的行为。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/934104.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号