まず始めに明言しておくと、lambda式でデータメンバーはキャプチャーできない。lambda式は、ローカル変数しかキャプチャできない。従って、このブログのタイトルは、すでに間違っている。ではなぜこんなタイトルなのか。それは。lambda式では、データメンバーを使うことが出来るからだ。これは、誤解されやすい。
struct C { int x ; void f() { [=]{ return x ; } ; } } ;
これは、完璧にwell-formedなC++0xのコードである。ご覧のように、データメンバーが使える。「ほれ見ろ使えるじゃねぇか。するてぇと、データメンバーもキャプチャーできるに違ぇねぇ」と思われるかもしれない。しかし、データメンバーは、キャプチャーできない。
では、このlambda式は、何をキャプチャーしているのか。thisである。上の例は、以下のように書くこともできる。
struct C { int x ; void f() { [this]{ return this->x ; } ; } } ;
thisは、必ずコピーキャプチャーしなければならない。最も、thisはポインターなので、参照キャプチャーしたとしても、あまり使い道はない。ともかく、デフォルトキャプチャーを指定したlambda式の中で、非staticなデータメンバーを使うと、自動的にthisがキャプチャーされる。これは、[&]でも、同様である。thisを介してのアクセスなので、データメンバーは、参照される。コピーはされない。thisは、必ずコピーキャプチャーされる。しかし、ご存知のように、thisはポインターなので、データメンバーは、リファレンスキャプチャーしたように振舞う。
これは、非常に危険である。皆、[=]は、コピーキャプチャーであり、クロージャーオブジェクトを保持しても安全だと考えている。確かに、コピーされる。何がコピーされるかというと、thisである。
struct C { int x ; std::function< int (void) > f() { return [=]{ return x ; } ; } } ; int main() { std::function< int (void) > f ; { C c ; f = f.f() ; }// cの寿命終了のお知らせ f() ;// 実行時エラー、オブジェクトはすでに死んでいる }
このコードは、cの寿命が尽きた後に、クロージャーオブジェクトを呼び出しているので、実行時エラーになる。
これは、例外の少ない一番シンプルな設計である。この設計に嘘はない。lambda式は、基本的に、ローカル変数しかキャプチャーできない。データメンバーはキャプチャーできない。ただし、thisは特別にキャプチャーできる。したがって、データメンバーは、常に参照でアクセスされる。
では、thisではなく、どうしてもデータメンバー自体をキャプチャーしたい場合はどうするのか。これは、明示的にローカル変数を作ってやるしかない。
struct C { int x ; void f() { auto x = this->x ; [=]{ x ; } ; } } ;
もちろん、紛らわしければ、別の名前にしてもいい。
No comments:
Post a Comment