質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

710閲覧

c++: lambda につきまして

guriguri

総合スコア34

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/03/31 02:50

編集2020/03/31 02:52

質問内容

lambda内から外部の変数アクセスについて質問させてださい。

下記コードのfoo関数内でlambdaを利用しているのですが、lambdaの外部にあるbar変数にアクセスしたいのですができません。

c++

1typedef void (*CB)(int signal); 2 3void setSignalHandler(CB callback); 4 5void foo(Account *account) { 6 int bar; 7 setSignalHandler([](int signal){ 8 // ... = signal + bar; <------- bar にアクセスしたいができない! 9 }); 10}

そこで下記のようにローカル変数にアクセスできるようにラムダ導入子に=を指定して [=](int signal){...} としました。

c++

1... 2void foo(Account *account) { 3 int bar; 4 setSignalHandler([=](int signal){ <-------- ラムダ導入子を [=] に変更! 5 // ... = signal + bar; 6 }); 7}

しかし、今度はsetSignalHandler(CB callback) の引数の型に合わないとエラーが発生しました。
このような場合はどこを改善すればsetSignalHandler内から外部の bar 変数にアクセスできるようになるのでしょうか?

typedef void (*CB)(int signal); このtypedefの定義を lambda に合わせることができればいいのかな?とネットを調べたのですがよく分かりませんでした。

以上、よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

こんには。

SaitoAtsushiさんの回答でFAなのですが、少し補足してみます。

ラムダ式は関数オブジェクトの一種です。

例えば、barを個別にコピーキャプチャする場合は、[bar](int signal){...}と記述しますが、これは下記のような関数オブジェクトです。(実際にはHogeHogeクラスではなく、名前のないクラスとなります。)

C++

1class HogeHoge 2{ 3 int bar_; 4public: 5 HogeHoge(int bar) : bar_(bar) { } 6 void operator()(int signal) { std::cout << signal+bar_; } 7};

(キャプチャで[=][&]と指定すると、コンパイラがラムダ式内でアクセスしているローカル変数を抽出し、それらに対応したコンストラクタを自動生成してくれます。[=]なら値渡し、[&]なら参照渡しがデフォルトとなります。)

そして、setSignalHandler([bar](int signal){...});との記述は、次のような記述と事実上同じです。

C++

1int bar=123; 2setSignalHandler(HogeHoge(bar));

HogeHoge(bar)はHogeHogeクラスのインスタンスです。CBとは全く異なる型であることが分かると思います。

SaitoAtsushiさんの回答のように、std::functionなら、通常の関数や関数オブジェクトを受け取ることができます。wandbox


【余談ですが】
因みに、残念なことにstd::functionは一般の非staticなメンバ関数を直接受け取ることができません。
例えばラムダ式で中継する等対策はありますが、参照キャプチャするとはまりやすいので要注意です。

投稿2020/03/31 04:30

編集2020/03/31 04:32
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guriguri

2020/03/31 04:58

なるほどです。無名クラス?に変換されているのですね。 理解が深まりました。ありがとうございました!
guest

0

ベストアンサー

ラムダ式は operator() を実装したクラスの定義とその呼び出しを同時にしたかのように振舞う構文糖ですが、外側の変数をキャプチャしていない限り関数ポインタに型変換が可能であるというルールがあります。 この場合は bar をキャプチャしてしまったがためにそのルールが要求する条件にあてはまらなくなったのでエラーになったわけです。

std::function を使うのが常道の解決方法です。

投稿2020/03/31 03:03

SaitoAtsushi

総合スコア5446

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guriguri

2020/03/31 03:55 編集

ご返信ありがとうございます。 早速下記のように std::function を使ってみました。 void setSignalHandler(std::function<void(int)> callback); void foo(Account *account) { int bar; <--- bar=100 setSignalHandler([&](int signal){ // ... = signal + bar; <------- bar=538968096 全く違う値に...なぜ!? }); } このようにしたところコンパイルエラーは出ませんでした。 ただ、barの値が全く違う値(外の値は100, lambda内では538968096)になってしまい意図した挙動と異なるようです。 全く分からないです。仰ったのはこういうことではないのでしょうか?
guriguri

2020/03/31 03:59

[=]とすることで値渡しでアクセスできました! [&]だとアドレス?だったのかな・・・。
SaitoAtsushi

2020/03/31 04:37

& だと参照になります。 foo 関数が終了した時点で bar は寿命を終えているわけですからその時点から bar の参照はいわゆるダングリング参照になってしまいます。 与えられた関数を setSignalHandler の中で呼び出す分には全く問題ありませんが、保存しておいて後で呼び出そうとするとデタラメなことになるわけです。 言語仕様的に言えばダングリング参照にアクセスするのは「未定義動作」です。 いかなる予測不能な振舞いをしてもかまわないというルールであり、クラッシュしても暴走しても仕様通りです。 実質的にすべきではないことだと考えてください。
guriguri

2020/03/31 04:59

> foo 関数が終了した時点で bar は寿命を終えているわけですから あ、確かに・・・。なるほどです! ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問