5

私はC++をかなりよく知っています。私は他の言語でラムダとクロージャを使用しました。私の学習では、C++でこれらを使用して何ができるかを確認したいと思いました。

「危険」を完全に理解し、コンパイラがこれを拒否することを期待して、参照によって関数スタック変数を使用して関数にラムダを作成し、ラムダを返しました。コンパイラはそれを許可し、奇妙なことが起こりました。

なぜコンパイラはこれを許可したのですか?これは、コンパイラが私が非常に、非常に悪いことをしたことを検出できず、結果が単に「未定義の動作」であるという問題ですか?これはコンパイラの問題ですか?スペックにはこれについて何か言いたいことがありますか?

MacPortsがインストールされたgcc4.7.1と-std=c ++ 11コンパイルオプションを使用して、最近のMacでテストされました。

使用したコード:

#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

参照によってローカル変数をキャプチャするラムダを返すことは、ローカル変数への参照を直接返すことと同じです。その結果、未定義の動作が発生します。

5.1.2ラムダ式[expr.prim.lambda]

22-[注:エンティティが暗黙的または明示的に参照によってキャプチャされた場合、エンティティの存続期間が終了した後に対応するラムダ式の関数呼び出し演算子を呼び出すと、未定義の動作が発生する可能性があります。—エンドノート]

具体的には、この場合の未定義の動作は、左辺値から右辺値への変換です。

4.1左辺値から右辺値への変換[conv.lval]

1-非関数、非配列型Tのglvalue(3.10)は、prvalueに変換できます。Tが不完全な型の場合、この変換を必要とするプログラムの形式が正しくありません。glvalueが参照するオブジェクトがタイプTのオブジェクトではなく、Tから派生したタイプのオブジェクトでもない場合、またはオブジェクトが初期化されていない場合、この変換を必要とするプログラムの動作は未定義です。

コンパイラは、この形式の未定義動作を診断する必要はありませんが、ラムダのコンパイラサポートが向上するにつれて、コンパイラはこのケースを診断して適切な警告を提供できるようになる可能性があります。

ラムダクロージャタイプは明確に定義されており、不透明であるため、例は次のようになります。

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

一般的に、C ++は、プログラマーの責任とし、プログラマーが効率的に解決できるようにする機能(可変ラムダキャプチャ、移動セマンティクスなど)を提供することにより、 funarg問題unique_ptrを解決します。

于 2012-09-17T16:21:33.563 に答える