25

このコードはJS開発者にとって未知のものではありません

function get_counter()
{
    return (
        function() {
            var c = 0;
            return function() { return ++c; };
        })();
}

基本的に、さまざまな列挙子を作成するを作成します。それで、新しいラムダセマンティクスを使用してC ++ 11で同じことができるかどうか疑問に思いましたか?残念ながらコンパイルされないこのC++を書くことになりました!

int main()
{
    int c;
    auto a = [](){
        int c = 0;
        return [&](){
            cout << c++;
        };
    };
    return 0;
}

だから私はそれをコンパイルするための回避策があるかどうか、そしてコンパイラがこのコードを正しく実行する方法があるかどうか疑問に思っていましたか?つまり、個別の列挙子を作成する必要がありますが、ガベージ(未使用のc変数)も収集する必要があります。

ちなみに、私はVS2012コンパイラを使用していますが、次のエラーが発生します。

Error   2   error C2440: 'return' : cannot convert from 'main::<lambda_10d109c73135f5c106ecbfa8ff6f4b6b>::()::<lambda_019decbc8d6cd29488ffec96883efe2a>' to 'void (__cdecl *)(void)'    c:\users\ali\documents\visual studio 2012\projects\test\test\main.cpp   25  1   Test
4

6 に答える 6

22

コードには、ぶら下がっている参照が含まれているというバグがあります。参照は、c外部ラムダのローカル変数を参照します。これは、外部ラムダが戻るときに破棄されます。

mutable値によるラムダキャプチャを使用して記述する必要があります。

auto a = []() {
    int c = 0;
    return [=]() mutable {
        cout << c++;
    };
};

これは、ポストスタンダードの拡張機能に依存して、return-type-deducinglambdaで複数のステートメントを許可します。複数のステートメントが含まれている場合、ラムダがリターンタイプを推測できないようにする理由はありますか?これを修正する最も簡単な方法は、ラムダに1つのステートメントのみが含まれるようにパラメーターを指定することです。

auto a = [](int c) {
    return [=]() mutable {
        cout << c++;
    };
};

残念ながら、デフォルトのパラメーターはラムダでは許可されていないため、これをとして呼び出す必要がありますa(0)。あるいは、読みやすさを犠牲にして、ネストされたラムダ呼び出しを使用することもできます。

auto a = []() {
    return ([](int c) {
        return [=]() mutable {
            cout << c++;
        };
    })(0);
};

これが機能する方法は、a実行時に内部ラムダがすべての参照変数をそのクロージャー型のインスタンスにコピーすることです。これは次のようになります。

struct inner_lambda {
    int c;
    void operator()() { cout << c++; }
};

次に、クロージャタイプのインスタンスが外部ラムダによって返され、呼び出すことができ、呼び出されcたときにそのコピーを変更します。

全体として、(固定された)コードは次のように変換されます。

struct outer_lambda {
    // no closure
    struct inner_lambda {
        int c;    // by-value capture
        // non-const because "mutable"
        void operator()() { cout << c++; }
    }
    // const because non-"mutable"
    inner_lambda operator()(int c) const {
        return inner_lambda{c};
    }
};

c参照によるキャプチャとして残した場合、これは次のようになります。

struct outer_lambda {
    // no closure
    struct inner_lambda {
        int &c;    // by-reference capture
        void operator()() const { cout << c++; } // const, but can modify c
    }
    inner_lambda operator()(int c) const {
        return inner_lambda{c};
    }
};

inner_lambda::cこれは、ローカルパラメータ変数へのぶら下がり参照ですc

于 2012-09-28T12:14:08.013 に答える
9

変数が存在しなくなると、参照によってキャプチャするラムダがキャプチャされた変数を使用できなくなるのは、C++の自然な制限です。cしたがって、コンパイルしても、自動変数は戻り時に破棄されるため、このラムダを表示されている関数から返すことはできません(これもラムダですが、関係ありません) 。

必要なコードは次のとおりです。

return [=]() mutable {
    cout << c++;
};

私はそれをテストしておらず、どのコンパイラバージョンがそれをサポートしているかわかりませんが、それは値によるキャプチャであり、キャプチャmutableされた値はラムダによって変更できると言えます。

したがって、呼び出すaたびに、0から始まる独自のカウントを持つ異なるカウンターを取得します。そのカウンターを呼び出すたびに、。の独自のコピーがインクリメントされますc。私がJavascriptを理解している限り(それほど遠くない)、それはあなたが望むものです。

于 2012-09-28T12:11:03.817 に答える
7

問題は、コンパイラが外部ラムダ(に割り当てられたものa)の戻り型を推測できないことです。これは、単純な1行以上の戻りで構成されているためです。しかし残念ながら、内部ラムダのタイプを明示的に示す方法もありません。したがって、を返す必要がありますstd::function。これには、追加のオーバーヘッドが伴います。

int main()
{
    int c;
    auto a = []() -> std::function<void()> {
        int c = 0;
        return [=]() mutable {
            std::cout << c++;
        };
    };
    return 0;
}

そしてもちろん、スティーブがすでに彼の答えで説明したように、あなたは価値によって捕らえる必要があります。

編集:void(*)()正確なエラーが、返された内部ラムダを(関数へのポインター)に変換できないことである理由についてはvoid()、ラムダの実装についてあまり洞察がないため、推測しかありませんが、それは確かではありません安定しているか、標準に準拠しています。

しかし、VCは少なくとも内部ラムダの戻り型を推測しようとし、呼び出し可能オブジェクトを返すことを認識していると思います。しかし、どういうわけか、この内側のラムダがキャプチャしない(または内側のラムダのタイプを判別できない)と誤って想定しているため、外側のラムダに単純な関数ポインターを返すようにします。これは、内側のラムダが機能しない場合でも実際に機能します。何でもキャプチャします。

編集:そして彼のコメントのecatmurの状態のように、通常の関数には自動戻り型の推論がないため、(ラムダの代わりに)std::function実際の関数を作成するときにaを返すことも必要です。get_counter

于 2012-09-28T12:19:43.220 に答える
3

最初に知っておくべきことは、構文をコンパイルしても、セマンティクスが異なるということです。参照によってキャプチャするC++ラムダでは、単なる参照をキャプチャしますが、その参照によってバインドされたオブジェクトの有効期間は延長されません。つまり、の存続c期間は、囲んでいるラムダの存続期間にバインドされます。

int main()
{
    int c;
    auto a = [](){
        int c = 0;
        return [&](){
            return ++c;
        };
    }();                     // Note: added () as in the JS case
    std::cout << a() << a();
    return 0;
}

()外部ラムダが評価されるように欠落を追加した後、問題はc、返されたラムダで参照によって保持されているものが、完全な式の評価後に無効になることです。

そうは言っても、追加の動的割り当て(JSの場合と同等)を犠牲にしてそれを機能させることはそれほど複雑ではありません。

int main()
{
    int c;
    auto a = [](){
        std::shared_ptr<int> c = std::make_shared<int>(0);
        return [=](){
            return ++(*c);
        };
    }();                     // Note: added () as in the JS case
    std::cout << a() << a();
    return 0;
}

これは、期待どおりにコンパイルおよび動作するはずです。内部ラムダが解放される(aスコープ外になる)たびに、カウンターはメモリから解放されます。

于 2012-09-28T12:18:23.467 に答える
2

これはg++4.7で動作します

#include <iostream>
#include <functional>                                                                           

std::function<int()> make_counter() {
    return []()->std::function<int()> {
        int c=0;
        return [=]() mutable ->int {
            return  c++ ;
        };  
    }();
}   


int main(int argc, char * argv[]) {
    int i = 1;
    auto count1= make_counter();
    auto count2= make_counter();

    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count2=" << count2() << std::endl;
    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count2=" << count2() << std::endl;
    return 0;
}

Valgrindはこれについてまったく文句を言いません。make_counterを呼び出すたびに、valgrindは追加の割り当てと解放を報告するため、ラムダメタプログラミングコードが変数cのメモリの割り当てコードを挿入していると想定します(デバッガーを確認できると思います)。これはCxx11に準拠しているのか、それともg++固有なのか疑問に思います。Clang 3.0は、std :: functionがないため、これをコンパイルしません(おそらく、ブースト関数を使用してみることができます)。

于 2013-02-23T18:05:21.247 に答える
0

これが遅いことはわかっていますが、C ++ 14以降では、ラムダキャプチャを初期化できるようになり、より単純なコードになります。

auto a = []() {
    return [c=0]() mutable {
        cout << c++;
    };
};
于 2020-04-14T20:57:06.703 に答える