【发布时间】:2019-06-15 19:35:51
【问题描述】:
我最近开始为我正在开发的库添加异步支持,但遇到了一个小问题。我从这样的事情开始(稍后的完整上下文):
return executeRequest<int>(false, d, &callback, false);
那是在添加异步支持之前。我试图将其更改为:
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
但是编译失败。
MCVE:
#include <iostream>
#include <future>
int callback(const int& t) {
std::cout << t << std::endl;
return t;
}
class RequestData {
private:
int x;
public:
int& getX() {
return x;
}
};
class X {
public:
template <typename T>
T executeRequest(bool method, RequestData& requestData,
std::function<T(const int&)> parser, bool write) {
int ref = 42;
std::cout << requestData.getX() << std::endl;
return parser(ref);
}
int nonAsync() {
// Compiles
RequestData d;
return this->executeRequest<int>(false, d, &callback, false);
}
std::future<int> getComments() {
RequestData d;
// Doesn't compile
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
}
};
int main() {
X x;
auto fut = x.getComments();
std::cout << "end: " << fut.get() << std::endl;
}
它失败了:
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1505:56: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
typedef typename result_of<_Callable(_Args...)>::type result_type;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:1709:49: note: in instantiation of template class 'std::_Bind_simple<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>' requested here
__state = __future_base::_S_make_async_state(std::__bind_simple(
^
main.cpp:33:25: note: in instantiation of function template specialization 'std::async<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool), X *, bool, RequestData &, int (*)(const int &), bool>' requested here
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
^
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1525:50: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
typename result_of<_Callable(_Args...)>::type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
2 errors generated.
两者之间唯一的实际区别(至少我可以看到)是我需要显式传递this,因为我正在引用一个成员函数
我玩了一下,发现如果我用const RequestData& 替换它,它突然就被允许了。但它反而会在其他地方导致问题,因为 getter 不是 const。至少从我能找到的情况来看,我需要使它成为一个 const 函数,这对 getter 本身来说很好,但我也有一些 setter,这意味着我不能这样做。
无论如何,我想我可以试试std::bind。我将异步调用替换为:
auto func = std::bind(&X::executeRequest<int>, this, false, d, &callback, false);
return std::async(std::launch::async, func);
而且,出于某种原因,it worked。
在这里让我感到困惑的是,它两次都使用相同的参数(如果计算非异步变体,则全部三次),并且考虑到this 参数,给定我正在调用的函数是一个成员函数。
我深入挖掘,找到了一些替代解决方案(尽管参考了std::thread),它们使用了std::ref。我知道std::async 在后台运行std::thread,所以我挖出了the documentation:
线程函数的参数按值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用
std::ref或std::cref)。 (强调我的)
这是有道理的,并解释了它失败的原因。我假设std::async 也受此限制,并解释了它失败的原因。
然而,挖掘std::bind:
bind 的参数被复制或移动,并且永远不会通过引用传递,除非包装在
std::ref或std::cref中。 (强调我的)
我不使用std::ref(或者如果我用const、std::cref替换),但至少如果我理解文档正确,这两个都应该无法编译。 example on cppreference.com 也可以在没有 std::cref 的情况下编译(在 Coliru 中使用 Clang 和 C++ 17 进行了测试)。
这是怎么回事?
如果重要的话,除了 coliru 环境,我最初在 Docker 中重现了这个问题,运行 Ubuntu 18.04 和 Clang 8.0.1(64 位)。在这两种情况下都针对 C++ 17 编译。
【问题讨论】:
-
未来问题的旁注:虽然您的示例是可重现的,但应尽可能减少,例如,godbolt.org/z/_7gg2Q
-
@Holt 在保持可运行状态并尽可能接近我的实际代码的同时尽可能小。我决定坚持上课以防万一(尽管删除它并没有太多 IIRC)。而且我的实际代码仍然比这更大(并且更明智)。点虽然 - 谢谢