13

私はこのコードを持っています:

void foo(void (*bar)()) {
    bar();
}

int main() {
    foo([] {
        int x = 2;
    });
}

しかし、私はこれが次のような運命をたどるのではないかと心配しています。

struct X { int i; };

void foo(X* x) {
    x->i = 2;
}

int main() {
    foo(&X());
}

これはローカル変数のアドレスを取ります。

最初の例は完全に安全ですか?

4

3 に答える 3

20

何もキャプチャしないラムダは、同じ引数リストと戻り値を持つ関数ポインターに暗黙的に変換できます。これを実行できるのは、キャプチャのないラムダだけです。それが何かをキャプチャする場合、彼らはできません。

標準のその部分を実装しなかったVS2010を使用している場合を除きます。これは、コンパイラを作成しているときにまだ存在していなかったためです。

于 2013-03-27T11:04:51.260 に答える
5

ニコルの完全に正しい一般的な答えに加えて、私はあなたの特定の恐れについていくつかの見解を追加します:

ただし、これがローカル変数のアドレスを取得する...と同じ運命をたどるのではないかと心配しています。

もちろんそうですが、ローカル変数/ラムダを定義しfooた周囲の関数(この場合)は呼び出された関数よりも長持ちするため、内部で呼び出すだけで(構造体の例が完全に機能するのと同じように)、これはまったく問題ありません。とにかくmain関数( )。foo後で使用するためにそのローカル変数またはラムダポインタを安全にする場合にのみ、問題になる可能性があります。それで

最初の例は完全に安全ですか?

はい、2番目の例もそうです。

于 2013-03-27T11:42:07.153 に答える
5

はい、最初の例は、キャプチャのないラムダ式を含む完全な式の評価中に作成されたすべての一時的なものの存続期間に関係なく、安全であると思います。

ワーキングドラフト(n3485)による5.1.2 [expr.prim.lambda] p6

ラムダキャプチャのないラムダ式のクロージャタイプには、クロージャタイプの関数呼び出し演算子と同じパラメータとリターンタイプを持つ関数へのポインタへのパブリック非仮想非明示const変換関数があります。この変換関数によって返される値は、呼び出されたときに、クロージャ型の関数呼び出し演算子を呼び出すのと同じ効果を持つ関数のアドレスでなければなりません。

上記の段落は、ラムダ式の評価後に期限切れになる関数へのポインターの有効性については何も述べていません。

たとえば、次のように機能することを期待します。

auto L = []() {
   return [](int x, int y) { return x + y; };
};

int foo( int (*sum)(int, int) ) { return sum(3, 4); }


int main() {
  foo( L() );
}

clangの実装の詳細は確かにC++の最後の言葉ではありませんが(標準はそうです)、気分が良くなる場合、これをclangで実装する方法は、ラムダ式が解析され、意味的に分析されるときに、ラムダ式が発明され、ラムダの関数呼び出し演算子と同様のセマンティクスで静的関数がクラスに追加されます。したがって、「L()」によって返されるラムダオブジェクトの存続期間は「foo」の本体内で終了しますが、関数へのポインターへの変換は、まだ有効な静的関数のアドレスを返します。

やや類似したケースを考えてみましょう。

struct B {
   static int f(int, int) { return 0; }
   typedef int (*fp_t)(int, int);
   operator fp_t() const { return &f; }
};
int main() {
  int (*fp)(int, int) = B{};
  fp(3, 4); // You would expect this to be ok.
}

私は確かにcore-c++の専門家ではありませんが、FWIW、これは標準の文字の私の解釈であり、防御可能だと感じています。

お役に立てれば。

于 2013-03-28T04:53:11.200 に答える