将功能直接传递给std :: async和使用std :: bind有什么区别?

时间:2019-06-15 19:35:51

标签: c++ stdasync

我最近开始向正在使用的库中添加异步支持,但是遇到了一个小问题。我从这样的事情开始(稍后是完整的上下文):

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.

Live example

两者之间的唯一实际区别(至少我可以看到)是我需要显式传递this,因为我引用的是成员函数

我玩了一下,设法找到了,如果我用const RequestData&代替它,突然被允许了。但这反而会导致其他问题,因为吸气剂不是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

让我感到困惑的是,它两次都使用相同的参数(如果计算非异步变量,则全部使用3次),并且在给定函数I的情况下考虑了this参数m调用是成员函数。

我更深入地研究,找到了一些使用std::thread的替代解决方案(尽管引用了std::ref)。我知道std::async在引擎盖下运行std::thread,所以我挖了the documentation

  

线程函数的参数按值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用std::refstd::cref)。   (强调我的)

这很有道理,并解释了失败的原因。我假设std::async也受此限制,并说明了失败的原因。

但是,挖掘std::bind

  

要绑定的参数将被复制或移动,并且除非封装在std::refstd::cref中,否则绝不会通过引用传递。   (强调我的)

我都不使用std::ref(或者如果我用conststd::cref代替),但是至少如果我理解文档的权利,这两个都应该失败编译。 example on cppreference.com也可以不使用std::cref进行编译(在Coliru中使用Clang和C ++ 17进行了测试)。

这是怎么回事?

如果重要的话,除了coliru环境之外,我最初是在Docker上重现该问题的,它使用Clang 8.0.1(64位)运行Ubuntu 18.04。在两种情况下均针对C ++ 17进行编译。

2 个答案:

答案 0 :(得分:5)

该标准略有不同。对于std::bind

  

要求:is_­constructible_­v<FD, F>true。   对于Ti中的每个BoundArgsis_­constructible_­v<TDi, Ti>应为true。   INVOKE(fd, w1, w2, …, wN)([func.require])应该是某些值w1w2,…,wN的有效表达式,其中N的值是{ {1}}。   如下所述,呼叫包装器sizeof...(bound_­args)的cv限定词cv不应是volatile或const volatile。

     

返回:参数转发调用包装器g([func.require])。   g的效果应为

g(u1, u2, …, uM)

INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN)) ,...,v1具有特定类型。在您的情况下,重要的是与vN对应的存储变量的类型为d,类型为std::decay_t<RequestData&>。在这种情况下,您可以使用左值RequestData轻松调用executeRequest<int>

RequestData的要求要强得多:

  

要求:std::asyncF中的每个Ti必须满足 Cpp17MoveConstructible 要求,并且

Args

巨大的区别是decay-copy。对于INVOKE(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...) // see [func.require], [thread.thread.constr] ,您将获得以下信息:

d

这是对decay-copy(std::forward<RequestData&>(d)) 函数的调用(仅博览会),其返回类型为decay-copy,因此返回std::decay_t<RequestData&>,这就是编译失败的原因。


请注意,如果您使用RequestData,则该行为将是不确定的,因为std::ref的生存期可能在调用d之前结束。

答案 1 :(得分:2)

  

让我感到困惑的是,两次都使用相同的参数

但是它两次都转发不同。调用异步版本时,将调用as if by calling

std::invoke(decay_copy(std::forward<Function>(f)), 
            decay_copy(std::forward<Args>(args))...);

争论变成了类似于临时的东西!因此,引用RequestData& requestData无法绑定到其参数。 const引用,右值引用或按值平原参数在这里可以工作(例如在绑定中),但非const左值引用不能。

std::bind的调用方式不同。它也存储副本,但是"the ordinary stored argument arg is passed to the invokable object as lvalue argument[sic]"具有从bind对象本身派生的参数的cv限定。由于std::bind创建了一个非常量绑定对象,因此为requestData提供了一个非常量左值。引用很高兴地与之绑定。