5

我对 C++ 相当了解。我在其他语言中使用过 lambda 和闭包。为了我的学习,我想看看在 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;
}

结果:

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
4

1 回答 1

10

Returning a lambda that captures a local variable by reference is the same as returning a reference to a local variable directly; it results in undefined behaviour:

5.1.2 Lambda expressions [expr.prim.lambda]

22 - [ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note ]

Specifically, the undefined behaviour in this case is in lvalue-to-rvalue conversion:

4.1 Lvalue-to-rvalue conversion [conv.lval]

1 - A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

The compiler is not required to diagnose this form of undefined behaviour, although as compiler support for lambdas improves it is likely that compilers will be able to diagnose this case and offer an appropriate warning.

Since lambda closure types are well defined, just opaque, your example is equivalent to:

struct lambda {
    int &y;
    lambda(int &y): y(y) {};
    int operator()(int toAdd) {
        y += toAdd;
        return y;
    };
} f{y};
return f;

In general terms, C++ solves the funarg problem by making it the responsibility of the programmer and providing facilities (mutable lambda capture, move semantics, unique_ptr etc.) to allow the programmer to solve it efficiently.

于 2012-09-17T16:21:33.563 回答