2016-11-28 4 views
0

これはかなり混乱するかもしれませんが、私はBoostテストフレームワークを使ってユニットテストを書いています。私は、特定のコールバックが期待どおりに実行されたことをテストする変数をインクリメントするだけです。ラムダキャプチャされた変数のゴミ値がコールバック

これは、テストコードの抜粋である:

uint32_t nSuccessCallbacks = 0; 
    uint32_t nFailureCallbacks = 0; 
    auto successCallback = [&nSuccessCallbacks, this] { 
    std::cout << "Running success callback...\n"; 
    ++nSuccessCallbacks; 
    }; 

    auto failureCallback = [&nFailureCallbacks, this] (const std::string& str) { 
    std::cout << "Error code: " << str << "\n"; 
    std::cout << "Running failure callback...\n"; 
    ++nFailureCallbacks; 
    }; 

    dest.advertise(rr, successCallback, failureCallback); 

advertiseの定義:

void 
NfdRibReadvertiseDestination::advertise(nfd::rib::ReadvertisedRoute& rr, 
             std::function<void()> successCb, 
             std::function<void(const std::string&)> failureCb) 
{ 
    m_controller.start<ndn::nfd::RibRegisterCommand>(
    ControlParameters().setName(rr.getPrefix()).setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT).setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT), 
    [&] (const ControlParameters& cp) { successCb(); }, 
    [&] (const ControlResponse& cr) { failureCb(cr.getText()); }); 
} 

ちょうど参考のため、destがテストフィクスチャに定義されています。

nSuccessCallbacksを変更できません。コールバックが呼び出されるたびに、正しく処理されますが、コールバックが終了してdest.advertise()の後のコードに入っていれば、値は0です。コールバックラムダには正常に到達しますが、gdbはその変数が範囲。私はthisのすべてのキャプチャ、特定のキャプチャ、ミキシングのすべての合理的な組み合わせを試してみました。キャプチャ句が間違って変数をキャプチャする理由はわかりません。私の最高の推測は、ラムダが別のラムダに渡されるので、最初のキャプチャ節は失われるということですか?

EDIT:インターフェイスオブジェクトがデータを受け取ったときにコールバックが実行されます。テストの後半で私たちはそれを模倣し、重要ではなかったので、私はそれを含めないことを選んだ。

+0

私たちは水晶球に​​特定の問題を尋ねることになっていますか? – SergeyA

+1

いいえ、キャプチャは "失われていません"。それらは参照によって捕捉される。これは、ラムダが実行されたときに、それらのカウンタがスコープ内にあることを意味します。彼らがすでに破壊されているならば、未定義の行動をしてください! –

+0

ごみがどのように捕獲されているかを記述していません。上記のコードはラムダを決して実行しないので、そのようなイベントを説明することはできません。したがって、あなたは共有していない別の場所で問題が発生します。 – Yakk

答えて

1

水晶球を使用すると、参照によって何かをキャプチャする多くのスコープ(advertiseまたは "テストコード実行"のいずれか)を終了した後にラムダが実行されます。したがって、参照によって取得された変数はスコープとUBの結果を残しており、ゴミが表示されます。

投稿されたコードは実際にはラムダを実行しないので、明らかに投稿されたコードにラムダが含まれているような問題はありません。

ラムダまたはそのコピーが現在の有効範囲よりも長く存続する可能性がある場合は、原則として参照しないでください。コピーによるキャプチャ、または(C++ 14での)移動によるキャプチャ。このルールには例外がありますが、バグの簡単なソースです。

第2のルールとして、ラムダが現在のスコープを超えている場合は、キャプチャしたすべてのものを明示的にキャプチャします。デフォルトのキャプチャはありません。そうすれば、生涯(または指摘された生涯)が十分に長くないもの、例えばthisやいくつかのポインタやsomesuchのようなものがキャプチャされていることに驚くことはありません。

auto successCallback = [&nSuccessCallbacks, this] { 
    std::cout << "Running success callback...\n"; 
    ++nSuccessCallbacks; 
}; 

その範囲よりも長生きしない:この、この無コピーがいることを確認しその後

[successCb] (const ControlParameters& cp) { successCb(); }, 
[failureCb] (const ControlResponse& cr) { failureCb(cr.getText()); } 

:これを行う少なくとも

。そうであれば、キャプチャ方法を変更します。

+0

これはすべての点で敬意を表していますが、答えよりもコメントのように見えます。 – SergeyA

+0

@ SergeyA私は水晶球が正確であることを10:1の確率で与えます。 OPの問題は参照によって捕捉され、ラムダは捕捉された変数の範囲を越えている。私は特定できないので、クリスタルボール。 – Yakk

+0

私は同意します。しかし、私は適切に策定された質問を信じています:) – SergeyA

0

コールでは、名前を指定すると、要求を処理する非同期スレッドを開始します。残念ながら、これはラムダで参照によって取り込まれた変数は、アクセス時にすでに割り当て解除されていることを意味します。

C++では、コピーでキャプチャしたり、生涯の問題がなく、参照でキャプチャすることしかできませんが、ラムダが参照された変数よりも長生きすることがないようにする必要があります。

"上向きfunarg"問題(文脈から値を取ってコンテキストを越えて変数を取り込むラムダ)を正しく解決するには、ガベージコレクタが必要であり、C++はそれを提供しません。

解決策(ループを回避することに注意した場合)は、値をshared_ptrでキャプチャする必要がある変更可能な共有状態にキャプチャすることです。

関連する問題