lambda捕获变量中的垃圾值作为回调

时间:2016-11-28 20:39:44

标签: c++ lambda

我知道这可能相当令人困惑,但我正在使用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.我们成功到达回调lambda,但gdb报告没有范围内的这种变量。我已经尝试了this中所有捕获,特定捕获,混合以及删除等的所有合理组合。我不知道捕获子句错误捕获变量的原因。我最好的猜测是,由于lambda被传递到另一个lambda,第一个的capture子句会丢失吗?

编辑:当接口对象接收数据时执行回调。我们在测试的后期嘲笑它,并且不重要,所以我选择不包括它。

2 个答案:

答案 0 :(得分:1)

使用水晶球,你的lambda是在许多范围中的一个之后运行的,你可以通过引用捕获一些东西(advertise或你的&#34;测试代码exerpt&#34;)已经退出。因此,by-reference捕获变量已经离开了范围,而UB结果,你会看到垃圾。

您发布的代码实际上并没有运行lambda,所以发布的代码显然没有包含垃圾的lambda的问题。

作为一般规则,如果您的lambda或其任何副本可能比当前范围更长,则永远不要通过引用捕获。通过复制捕获,或者(在C ++ 14中)通过移动捕获。这条规则有例外,但它们很容易出现错误。

作为第二条规则,如果您的lambda超过当前范围,则显式捕获您捕获的所有内容。没有默认捕获。这样你就不会被被捕获的东西感到惊讶,这些东西的生命周期(或指向生命周期)不够长,比如this或某些指针或某些东西。

至少这样做:

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

然后确保这个,而不是这个的副本:

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

不会超出其范围。如果是,请更改捕获方式。

答案 1 :(得分:1)

start调用将在给定名称的情况下启动异步线程来处理请求。不幸的是,这意味着lambda中通过引用捕获的变量在被访问时已经被释放。

C ++只允许您按副本捕获(并且您没有生命周期问题)或通过引用捕获,但您必须确保lambda不会超过引用的变量。

正确解决&#34;向上的问题&#34;问题(一个lambda捕获一个变量 - 而不是一个值 - 来自一个上下文并超过上下文)需要一个垃圾收集器(一个堆栈不够),C ++不能提供一个。

解决方案(如果你注意避免循环)是按值shared_ptr捕获所需的可变共享状态。