C++ 11 Lambda闭包涉及通过引用的堆栈变量,允许留下范围但是获得未定义的行为?

Jon*_*eng 4 c++ lambda c++11

我非常了解C++.我在其他语言中使用过lambdas和closures.为了我的学习,我想看看我能用C++做些什么.

完全知道"危险"并期望编译器拒绝这一点,我通过引用使用函数堆栈变量在函数中创建了一个lambda并返回了lambda.编译器允许它并且发生了奇怪的事情.

为什么编译器允许这样做?这只是编译器无法检测到我做了非常非常糟糕的事情并且结果只是"未定义的行为"的问题吗?这是编译器问题吗?该规范有什么可说的吗?

在最近的Mac上测试,使用MacPorts安装的gcc 4.7.1和-std = c ++ 11编译选项.

使用的代码:

#include <functional>
#include <iostream>
using namespace std;

// This is the same as actsWicked() except for the commented out line
function<int (int)> actsStatic() {
  int y = 0;
  // cout << "y = " << y << " at creation" << endl;

  auto f = [&y](int toAdd) {
    y += toAdd;
    return y;
   };
  return f;
}

function<int (int)> actsWicked() {
  int y = 0;
  cout << "actsWicked: y = " << y << " at creation" << endl;

  auto f = [&y](int toAdd) {
    y += toAdd;
    return y;
   };
  return f;
}

void test(const function<int (int)>& f, const int arg, const int expected) {
  const int result = f(arg);
  cout << "arg: " << arg
       << " expected: " << expected << " "
       << (expected == result ? "=" : "!") << "= "
       << "result: " << result << endl;
}

int main(int argc, char **argv) {

  auto s = actsStatic();
  test(s, 1, 1);
  test(s, 1, 2);
  test(actsStatic(), 1, 1);
  test(s, 1, 3);

  auto w = actsWicked();
  test(w, 1, 1);
  test(w, 1, 2);
  test(actsWicked(), 1, 1);
  test(w, 1, 3);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

结果:

arg: 1 expected: 1 == result: 1
arg: 1 expected: 2 == result: 2
arg: 1 expected: 1 != result: 3
arg: 1 expected: 3 != result: 4
actsWicked: y = 0 at creation
arg: 1 expected: 1 == result: 1
arg: 1 expected: 2 == result: 2
actsWicked: y = 0 at creation
arg: 1 expected: 1 == result: 1
arg: 1 expected: 3 != result: 153207395
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 10

返回通过引用捕获局部变量的lambda与直接返回对局部变量的引用相同; 它导致未定义的行为:

5.1.2 Lambda表达式[expr.prim.lambda]

22 - [注意:如果通过引用隐式或显式捕获实体,则在实体的生命周期结束后调用相应lambda表达式的函数调用运算符可能会导致未定义的行为. - 尾注]

具体来说,在这种情况下,未定义的行为是左值到右值的转换:

4.1左值到右值的转换[conv.lval]

1 - 非函数非数组类型T的glvalue(3.10)可以转换为prvalue.如果T是不完整类型,则需要进行此转换的程序格式不正确.如果以该glvalue是指该对象不是类型T的对象,而不是源自T的类型的对象,如果该对象是未初始化,即必要这种转换的程序有未定义的行为.

编译器不需要诊断这种形式的未定义行为,尽管编译器对lambda的支持有所改进,但编译器很可能能够诊断出这种情况并提供适当的警告.

由于lambda闭包类型定义良好,只是不透明,因此您的示例等效于:

struct lambda {
    int &y;
    lambda(int &y): y(y) {};
    int operator()(int toAdd) {
        y += toAdd;
        return y;
    };
} f{y};
return f;
Run Code Online (Sandbox Code Playgroud)

一般而言,C++ 通过使程序员负责并提供设施(可变的lambda捕获,移动语义等)来解决funarg问题,unique_ptr以允许程序员有效地解决它.

  • 为了澄清,过时的引用(非常)很容易导致UB,但它本身不是UB(包括使用该引用绑定到另一个引用,就像返回/复制闭包对象时一样). (2认同)
  • @BodoThiesen考虑`[&i](bool b){if(b)++ i; }-仅当以`true`作为参数调用时,才能调用UB。 (2认同)
  • 哦,是的。在某个地方放置无效引用是没有问题的,仅使用无效引用(也称为取消引用)会强加UB。 (2认同)