【问题标题】:asio lambda with unique_ptr captureasio lambda 与 unique_ptr 捕获
【发布时间】:2015-06-11 11:42:44
【问题描述】:

我正在使用 asio 独立 1.10.6 和 vs2015 rc。

vs2015 支持 unique_ptr 捕获。所以我写了一些代码如下:

auto data = std::make_unique<std::string>("abc");
auto buffer = asio::buffer(data->c_str(), data->size());
asio::async_write(s, buffer, [data = std::move(data)](
  const asio::error_code& error, size_t byte_transferred) mutable {
  do_something(std::move(data), error, byte_transferred);
});

但是当我编译代码时,编译器说:

错误 C2280: .... 试图引用已删除的函数

据我了解,它说我尝试复制 lambda,因为 lambda 捕获了 std::unique_ptr,所以它是不可复制

让我困惑的是为什么 asio 想要复制 lambda 但不移动 lambda。

我的代码有什么问题?如何解决?

===========================

完整代码为:

void do_something(std::unique_ptr<std::string> data) { }

void compile_failed() {
  asio::io_service io_service;
  asio::ip::tcp::socket s(io_service);

  auto data = std::make_unique<std::string>("abc");
  auto buffer = asio::buffer(data->c_str(), data->size());

  asio::async_write(s, buffer, [data = std::move(data)](const asio::error_code& error,
    size_t byte_transferred) mutable {
    do_something(std::move(data));
  });
}

template<typename T > struct lambda_evil_wrap {
  mutable T ptr_;
  lambda_evil_wrap(T&& ptr) : ptr_(std::forward< T>(ptr)) {}
  lambda_evil_wrap(lambda_evil_wrap const& other) : ptr_(std::move(other.ptr_)) {}
  lambda_evil_wrap & operator=(lambda_evil_wrap& other) = delete;
};

void compile_success_but_very_danger() {
  asio::io_service io_service;
  asio::ip::tcp::socket s(io_service);

  auto data = std::make_unique<std::string>("abc");
  auto buffer = asio::buffer(data->c_str(), data->size());

  lambda_evil_wrap<std::unique_ptr<std::string>> wrapper(std::move(data));
  asio::async_write(s, buffer, [wrapper](const asio::error_code& error,
    size_t byte_transferred) mutable {
    do_something(std::move(wrapper.ptr_));
  });
}

int _tmain(int argc, _TCHAR* argv[])
{
    return 0;
}

作为代码,如果我将 unique_ptr 包装到一个可复制的对象中,编译就可以了。 但是 lambda_evil_wrap::lambda_evil_wrap(lambda_evil_wrap const& a) 真的很烂而且不安全。不知道asio作者是否写了一些代码如下:

Handler handler2(handler);
handler(...); // Crash here

【问题讨论】:

  • std::ref(lambda) ?
  • 你确定是这个问题吗?编译器还说什么吗?始终在问题中包含完整且未经编辑的错误日志。
  • 当然 std::ref(lambda) 可以让编译器满意。但是代码是错误的。当函数返回时,数据将被释放并在do_something中崩溃。
  • Joachim Pileborg:我确信这是因为 asio 希望处理程序可复制。以下代码有效: std::shared_ptr<:string> data2(std::move(data)); asio::async_write(... [data2](...){});
  • 奇怪的是只有 io_context::post 需要“移动赋值”,但是像 asio::async_connect 或 stable_timer::async_wait 这样的东西可以接受“移动构造函数”。

标签: c++ c++11 lambda boost-asio unique-ptr


【解决方案1】:

原代码中的错误是处理程序未能满足Handler type requirement,因为它不是CopyConstructible

处理程序必须满足CopyConstructible 类型(C++ 标准,20.1.3)的要求。

正如 Boost.Asio 对 movable handlers 的 C++11 支持中所述,在可能的情况下,Boost.Asio 会更喜欢移动构造函数而不是复制构造函数,但处理程序仍必须是可复制构造的:

[...] Boost.Asio 的实现将优先使用处理程序的移动构造函数而不是其复制构造函数。在某些情况下,Boost.Asio 可能能够消除对处理程序的复制构造函数的所有调用。但是,处理程序类型仍然需要是可复制构造的。

要解决此问题,可以考虑使用 std::shared_ptr 而不是 std::unique_ptr,从而使 lambda 可以复制构造。

【讨论】:

  • 我知道 shared_ptr 会起作用。但是 do_something(...) 想要一个 unique_ptr ,即使 shared_ptr.unique() 为真,我也无法将 shared_ptr 转换为 unique_ptr 。那么,如何解决呢?
  • @alpha 可以使用可复制类型 (std::shared_ptr&lt;std::unique_ptr&lt;std::string&gt;&gt;) 管理 API 需要的类型 (std::unique_ptr&lt;std::string&gt;)。虽然额外的间接级别可能并不理想,但它仍然提供了安全的语义。请参阅this 进行演示。
  • 不错。我刚读了你的代码。这是一个很好的解决方法。比我的 lambda_evil_wrap 好多了。
  • 我还有一些疑问:既然 C++ Std 要求处理程序类型必须是可复制的,为什么 C++14 增加了 lambda 移动捕获的功能?在 c++ 11 中,我们使用 lambda_evil_wrap 或将 unique_ptr 包装到 shared_ptr 中,就像您的代码一样,在 c++ 14 中,我们仍然需要使用这些包装器解决方案。
  • @alpha Boost.Asio 将其处理程序的要求定义为 CopyConstructible,而不是标准。 CopyConstructible 的 C++ 概念由标准定义。
猜你喜欢
  • 1970-01-01
  • 2012-01-04
  • 1970-01-01
  • 2015-03-04
  • 2021-09-24
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多