std::async 与共享指针模板化成员函数

Posted

技术标签:

【中文标题】std::async 与共享指针模板化成员函数【英文标题】:std::async with shared pointer templated member functions 【发布时间】:2019-07-09 12:39:40 【问题描述】:

我正在努力在一个项目中实现多线程,但是当涉及到 std::async 的一些更复杂的用途时,我遇到了困难。我想在模板化对象上调用成员函数并将参数作为参数传递,但是当模板包含共享指针时我无法使其工作。

在成员函数上使用 std::async 非常简单,甚至是模板化的成员函数。对于这种特定用途,我已经看到很多关于堆栈溢出的答案。我什至自己运行了一些测试用例:

#include <thread>
#include <future>
#include <iostream>

class Bar

  public:
    Bar () 
    double data;
;

template <typename T>
class Foo

  public:
    Foo () 
    T set_data (T d)  data = d; return data; 
  private:
    T data;
;

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)

  /**
   * Works fine
   */
  Foo<int> foo1;
  auto fut1 = std::async(std::launch::async, &Foo<int>::set_data, &foo1, 42);

  fut1.wait();
  std::cout << fut1.get() << std::endl;

  return 0;

此示例在 gcc 7.4.0 中编译得非常好,并按预期返回 42。

当我在模板中使用 shared_ptr 时,我的问题就出现了。使用与上面相同的 Foo 和 Bar 类:

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)

  /**
   * Doesn't work
   */
  auto foo2 = std::make_shared<Foo<std::shared_ptr<Bar>>>;
  auto br = std::make_shared<Bar>;
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, bar);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;

编译g++ -pthread test.cpp -o test时出现此错误

test.cpp: In function ‘int main(int, char**)’:
test.cpp:20:94: error: no matching function for call to ‘async(std::launch, std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)())’
   auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, bar);
                                                                                              ^
In file included from ./foo.h:2:0,
                 from test.cpp:1:
/usr/include/c++/7/future:1712:5: note: candidate: template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(std::launch, _Fn&&, _Args&& ...)
     async(launch __policy, _Fn&& __fn, _Args&&... __args)
     ^~~~~
/usr/include/c++/7/future:1712:5: note:   template argument deduction/substitution failed:
/usr/include/c++/7/future: In substitution of ‘template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(std::launch, _Fn&&, _Args&& ...) [with _Fn = std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>); _Args = Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)()]’:
test.cpp:20:94:   required from here
/usr/include/c++/7/future:1712:5: error: no type named ‘type’ in ‘class std::result_of<std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*(Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*)()))(std::shared_ptr<Bar>)>’
/usr/include/c++/7/future:1745:5: note: candidate: template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(_Fn&&, _Args&& ...)
     async(_Fn&& __fn, _Args&&... __args)
     ^~~~~
/usr/include/c++/7/future:1745:5: note:   template argument deduction/substitution failed:
/usr/include/c++/7/future: In substitution of ‘template<class _Fn, class ... _Args> std::future<typename std::result_of<typename std::decay<_Tp>::type(typename std::decay<_Args>::type ...)>::type> std::async(_Fn&&, _Args&& ...) [with _Fn = std::launch; _Args = std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*&)()]’:
test.cpp:20:94:   required from here
/usr/include/c++/7/future:1745:5: error: no type named ‘type’ in ‘class std::result_of<std::launch(std::shared_ptr<Bar> (Foo<std::shared_ptr<Bar> >::*)(std::shared_ptr<Bar>), Foo<std::shared_ptr<Bar> >*, std::shared_ptr<Bar> (*)())>’

我认为这可能是因为引用 & 没有按照模板的所有尖括号以正确的顺序工作,所以我尝试使用一些括号:

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)

  /**
   * Doesn't work
   */
  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;
  auto fut2 = std::async(std::launch::async, &(Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar);

  fut2.wait();
  std::cout << fut2.get().data << std::endl;

  return 0;

这会导致更短的错误,我也不明白。

test.cpp: In function ‘int main(int, char**)’:
test.cpp:20:75: error: invalid use of non-static member function ‘T Foo<T>::set_data(T) [with T = std::shared_ptr<Bar>]’
   auto fut2 = std::async(std::launch::async, &(Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar);

我很困惑为什么共享指针会突然产生影响,我猜这与类型推导有关?任何帮助表示赞赏。

编辑

感谢那些回复的人,这是解决方案。括号不是必需的,并且缺少一些 shared_ptrs。

#include "./foo.h"
#include <memory>

int main (int argc, char **argv)

  Foo<std::shared_ptr<Bar>> foo2;
  auto bar = std::make_shared<Bar>(2.5);
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data), &foo2, bar;

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;

【问题讨论】:

投票结束是一个错字。 set_data 采用 T。当Tshared_ptr&lt;Bar&gt; 时,您不能将它传递给Bar。没有从Barshared_ptr&lt;Bar&gt; 的转换 @NathanOliver 感谢您指出这一点。对您的第一条评论进行了编辑。错误仍然存​​在。 您的fut2.get().data 也有错字。 fut2.get()shared_ptr&lt;Bar&gt;。您需要fut2.get()-&gt;data 来获取Bar 的数据。 在调用 std::async 时尝试使用 foo2.get() 而不是 &amp;foo2。但总的来说,只需放弃这种语法并使用 lambda,它们更容易理解。 【参考方案1】:

有一些问题,我认为使用 lambda 可能会帮助您弄清楚发生了什么:

int main (int argc, char **argv)

  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;

  auto op = [foo2, bar]() mutable return foo2.set_data(std::make_shared<Bar>(bar));;
  auto fut2 = std::async(std::launch::async, op);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;

您必须将set_data 传递给shared_ptrFoo。其次,set_data 不是 const 限定的,因此您需要一个可变 lambda。 最后,未来通过get() 返回时,会给你一个shared_ptrBar,所以你需要运算符-&gt;。 您可以使代码更有效地在 lambda 中移动 Foo2Bar,但我试图保持答案简单,特别是因为我不知道您是否希望在您的用例中重新使用 @987654335 @ 和 Bar,但您可以考虑在 lambda 内部移动。

关于您的具体代码,以下是在 g++ 9.1 中使用 C++14 编译,参见https://godbolt.org/z/DFZLtb

int main (int argc, char **argv)

  Foo<std::shared_ptr<Bar>> foo2;
  Bar bar;
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, std::make_shared<Bar>());

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

  return 0;

您需要提供shared_ptr&lt;Bar&gt;as 参数而不是Bar,并且您需要删除Foo&lt;std::shared_ptr&lt;Bar&gt;&gt;::set_data 周围的括号。

【讨论】:

我也尝试过 lambda,但在我的用例中,我需要从 async 调用的函数中返回一些内容,而我无法让 lambda 正确执行此操作。不过,我会试一试。 尝试在godbolt.org/z/IYSLqQ 使用我的代码。如果你用 lambdas 发布你的试探性,我可以看看。 谢谢!我仍然犹豫要不要使用 lambda,因为我知道这应该通过传递函数以某种方式工作。我尝试更改您对共享指针的建议(如果您想查看原始问题中的编辑代码),但我仍然遇到相同的错误。 尝试 auto fut2 = std::async(std::launch::async, &Foo<:shared_ptr>>::set_data, &foo2, std::make_shared() ); ,见godbolt.org/z/qzRlxD【参考方案2】:

您的代码中只是有一些拼写错误,在第一个 shared_ptr 示例中,您将 shared_ptr&lt;Foo&gt; 而不是原始 ptr Foo* 传递给您的函数。在第二个示例中,第一个参数 Foo* 是正确的,但第二个参数是 Bar 而不是 shared_ptr&lt;Bar&gt;。这里有一个工作示例:

class Bar

  public:
    Bar () 
    double data;
;

template <typename T>
class Foo

  public:
    Foo () 
    T set_data (T d)  data = d; return data; 
  private:
    T data;
;

#include <future>

TEST(xxx, yyy) 
    Foo<std::shared_ptr<Bar> > foo;
    auto bar = std::make_shared<Bar>();
    bar->data = 42;
    auto futureResult = std::async(std::launch::async,
        &Foo<std::shared_ptr<Bar> >::set_data, &foo, bar);
    std::cout << futureResult.get()->data << std::endl;

附带说明,如果您需要对 Foo 类中的某些数据运行异步操作,最好提供在您的类中执行此异步操作并返回未来的接口。

std::future<T> runAsyncOper(const T& data);

【讨论】:

非常感谢您!当您从上一个答案发布它时,我刚刚找到了这个答案。也感谢最后一点鼓励正确封装,但我实际上并没有像示例那样完全实现我的代码,因此对类数据的操作不同。【参考方案3】:

您使用共享指针的第一个示例没有将共享指针传递给 std::async 作为最后一个参数,它传递了一个指向函数的指针(您没有添加括号)

int main()

    /**
    * Works :)
    */
    Foo<std::shared_ptr<Bar>> foo2;

    auto br = std::make_shared<Bar>();

    auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, &foo2, br);

    fut2.wait();
    std::cout << fut2.get()->data << std::endl;

    return 0;

【讨论】:

【参考方案4】:

问题是您将错误类型的参数传递给std::async 并错误地使用了std::make_shared

对代码的最小修改:

  auto foo2 = std::make_shared<Foo<std::shared_ptr<Bar>>>();
  auto bar = std::make_shared<Bar>();
  auto fut2 = std::async(std::launch::async, &Foo<std::shared_ptr<Bar>>::set_data, foo2, bar);

  fut2.wait();
  std::cout << fut2.get()->data << std::endl;

https://wandbox.org/permlink/3YXG56ahFKrZs8GB

【讨论】:

以上是关于std::async 与共享指针模板化成员函数的主要内容,如果未能解决你的问题,请参考以下文章

模板化成员函数不能从同一个类调用非模板化成员函数,C++

在 gcc 上将成员函数指针传递给模板成员函数时出现问题

为啥我不能从 gcc 中的前身模板化成员函数访问祖先方法?

使用模板化成员函数显式实例化模板类

模板化类的模板化成员方法可以在类定义之外定义吗

std::async 可以与模板函数一起使用吗