C++11 ラムダは素晴らしい!
しかし、1 つ欠けていることがあります。それは、変更可能なデータを安全に処理する方法です。
次の例では、最初のカウントの後に悪いカウントが返されます。
#include <cstdio>
#include <functional>
#include <memory>
std::function<int(void)> f1()
{
int k = 121;
return std::function<int(void)>([&]{return k++;});
}
int main()
{
int j = 50;
auto g = f1();
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
}
与える、
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
8365280
8365280
8365280
その理由は、f1()
リターンの後k
、範囲外ですが、まだスタック上にあるためです。したがって、最初のg()
実行k
は問題ありませんが、その後はスタックが破損し、k
その値が失われます。
したがって、C++11 で安全に返せるクロージャーを作成する唯一の方法は、クローズされた変数を明示的にヒープに割り当てることです。
std::function<int(void)> f2()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([=]{return (*o)++;});
}
int main()
{
int j = 50;
auto g = f2();
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
printf("%d\n", g());
}
ここで[=]
は、共有ポインタが参照ではなく確実にコピーされるようにするために使用されるため、メモリ処理が正しく行われk
ますg
。結果は希望通り、
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
122
123
124
変数を逆参照して変数を参照するのはかなり見苦しいですが、代わりに参照を使用できるはずです。
std::function<int(void)> f3()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&]{return p++;});
}
実際、奇妙なことに、これは私に、
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
0
1
2
3
理由はありますか?共有ポインタの参照を取得するのは礼儀正しくないかもしれませんが、これは追跡された参照ではないためです。参照をラムダ内に移動するとクラッシュすることがわかりました。
std::function<int(void)> f4()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([&]{int &p = *o; return p++;});
}
与える、
g++-4.5 -std=c++0x -o test test.cpp && ./test
156565552
/bin/bash: line 1: 25219 Segmentation fault ./test
いずれにせよ、ヒープ割り当てを介して安全に返せるクロージャを自動的に作成する方法があればいいのですが。たとえば、変数がヒープに割り当てられ、共有ポインターへの参照を介して参照される必要があることを示す代替手段があった[=]
とします。[&]
私が知ったときの最初の考えstd::function
は、クロージャーをカプセル化するオブジェクトを作成するため、クロージャー環境にストレージを提供できるということでしたが、私の実験では、これは役に立たないようです。
C ++ 11で安全に返せるクロージャは、それらを使用する上で最も重要になると思いますが、これをよりエレガントに実現する方法を知っている人はいますか?