C++でクロージャを作る2
コピーなどにコストがかかってパフォーマンスも然程良くないので使い道が微妙ですが、クラスもどきです。
こういうのはやはり参照で寿命管理が上手く扱える、ガベージコレクタありきの技術ですね、基本的にC++には向かないです。
メンバ変数の宣言と初期化は返す関数の外側で行います。
メンバ関数は返す関数の戻り値としてtupleにして持たせます。
メンバ関数へのアクセス周りはもうちょっとどうにか出来たらなぁと思いますが。
C++1yでジェネリックラムダが使えるようになるとtemplate化出来たりするので多少便利になるかもしれません。
コメントはあくまでも操作内容のイメージです。
#include <tuple> #include <cassert> int main() { static const auto factory = []() { // Declare and Init int x = 0; return [=]() mutable { // Functions return std::make_tuple( [&]() -> int { return x; }, [&](int&& value) -> void { x = value; } ); }; }; auto f1 = factory(); assert(std::get<0>(f1())() == 0); // f1.x == 0 std::get<1>(f1())(10); // f1.x = 10 assert(std::get<0>(f1())() == 10); //f1.x == 10 auto f2 = factory(); assert(std::get<0>(f2())() == 0); // f2.x == 0 std::get<1>(f2())(100); // f2.x = 100 assert(std::get<0>(f1())() == 10); // f1.x == 10 assert(std::get<0>(f2())() == 100); // f2.x == 100 }
ところでmake_tuple部分をforward_as_tupleにするとgccでassertに失敗しました。
メンバ関数ラムダ式本体を右辺値参照として受け取ってtupleを生成しても別に問題ないように思えるのですが。
@fimbul11 forward_as_tupleは参照のタプルを作りますが、参照されているラムダ式[&]()->int{return x;}の寿命は外から2番目のラムダ式を抜けた時点で終わっているのでダメです
— あいおーれーと@がんばらない (@iorate) August 17, 2013
iorateさんに指摘頂いたのですが、forward_as_tupleを使用するのは間違いでした。
forward_as_tupleは参照のtupleを生成するのですが、参照先が2番目のラムダ式を抜けた時点で死ぬので無効になります。
よって、参照先を呼び出した際に、undefined behaviorとなってしまいます。
オブジェクトの寿命を見誤っていました。
コピーを生成するmake_tupleが必要です。
追記
参照が有効なまま、tupleを参照から生成する方法を考えました。
外側でstd::functionを用いてプロトタイプ宣言に当たるものだけ行えば良かったです。
こちらの方がパフォーマンスが良い筈です。
#include <tuple> #include <cassert> #include <functional> int main() { static const auto factory = []() { // Declare and Init int x = 0; std::function<int()> get; std::function<void(int&&)> set; return [=]() mutable { // Functions get = [&]() -> int { return x; }; set = [&](int&& value) -> void { x = value; }; return std::forward_as_tuple(get, set); }; }; auto f1 = factory(); assert(std::get<0>(f1())() == 0); // f1.x == 0 std::get<1>(f1())(10); // f1.x = 10 assert(std::get<0>(f1())() == 10); //f1.x == 10 auto f2 = factory(); assert(std::get<0>(f2())() == 0); // f2.x == 0 std::get<1>(f2())(100); // f2.x = 100 assert(std::get<0>(f1())() == 10); // f1.x == 10 assert(std::get<0>(f2())() == 100); // f2.x == 100 }
掘り下げておいて難ですが、実用性あまり無い上にやばい(自分が発見出来てないだけで規格上、かなりの確率でundefined behaviorな気がする)と思うので個人的には遊び以外の用途での使用はすすめません。
時間がある際に規格的に念入りに検証してみます。