首页 > 解决方案 > 如何制作一个采用引用而不是副本的可变参数模板方法?

问题描述

我们有一个模板方法,它根据可用的系统线程数,使用线程池一般地并行执行一些成员函数。

此方法必须采用的两个参数是固定的,第一个是线程索引,第二个是线程数。

因此,这些方法可以执行以下操作:

for(i = threadIndex; i < workContainer.size(); i += numThreads) 
{
  // So this thread will only do every numThreads piece of the work from the workContainer, with no overlap between threads
}

这很好,这就是该方法的样子:


  template <class S, class Args>
  void parallelWork(S* self, void (S::*workfcn)(const unsigned, const unsigned, Args*), Args* args)
  {
    auto work = [&](const unsigned threadIndex, const unsigned nthreads, S* self, Args* args)

    {
      std::string* rc = nullptr;

      try
        {
          (self->*workfcn)(threadIndex, nthreads, args);
        }
      catch (std::exception& err)
        {
          rc = new std::string(err.what());
        }

      return rc;
    };

    ulong nthreads = size();
    std::vector<std::string*> rc(nthreads);

    if (1 == nthreads)
      {
        rc[0] = work(0, 1, self, args);
      }
    else
      {
        std::vector<std::future<std::string*>> frc(nthreads);

        for (unsigned i = 0; nthreads > i; ++i)
          frc[i] = enqueue(work, i, nthreads, self, args);

        // Wait for all
        for (unsigned i = 0; nthreads > i; ++i)
          rc[i] = frc[i].get();
      }

    for (unsigned i = 0; nthreads > i; ++i)
      {
        if (rc[i])
          {
            HELAS_ERROR(std::string("Thread ") + std::to_string(i) + ": " + *rc[i]); delete rc[i];
          }
      }
  }

然而,这是有限的。当前的解决方案要求我们有效地将所有参数(其中一些是引用)放入一个结构中,然后将指向该结构的指针传递给该parallelWork()方法。

如果我们可以直接传递方法应该采用的参数,那不是很好吗?IE,而不是这样做:

struct ThreadArgs 
{
  const std::vector<int>& inArg1;
  double inArg2;
  SomeClass* inarg3;
  std::vector<std::vector<double>> outArg1; // outer vector is per-thread
}
void MyClass::doFooInParallel() 
{
  std::vector<int> inArg1;
  double inArg2;
  SomeClass* cp = getSomeClass();
  std::vector<std::vector<double>> outArg1(numThreads);

  ThreadArgs args = ThreadArgs(inArg1, inArg2, cp, outArg1);

  parallelWork(this, &MyClass::foo, &args);
}

我们可以这样做:

void MyClass::doFooInParallel() 
{
  std::vector<int> inArg1;
  double inArg2;
  SomeClass* cp = getSomeClass();
  std::vector<std::vector<double>> outArg1(numThreads);

  parallelWork(this, &MyClass::foo, inArg1, inArg2, cp, outArg1);
}

似乎可变参数模板将是解决方案,将方法的开头更改为:

  template <class S, class... Args>
  void parallelWork(S* self,void (S::*workfcn)(const unsigned, const unsigned, Args... args),Args... args)
  {
    auto work = [&](const unsigned threadIndex, const unsigned nthreads,S* self, Args... args)

这确实符合我们当前对该方法的使用。但是,有一个问题 - 这些参数是按值传递而不是按引用传递。

对于输入,这可能还不错,有些可能会被复制省略消除。但是对于作为方法输出的传递变量(例如大小为 nthreads 的向量,每个线程的输出),这显然是行不通的——这意味着并行执行工作会导致实际上什么也没做。

我试着让他们像这样引用:

  template <class S, class... Args>
  void parallelWork(S* self,void (S::*workfcn)(const unsigned, const unsigned, Args&&... args),Args&&... args)
  {
    auto work = [&](const unsigned threadIndex, const unsigned nthreads,S* self, Args&&... args)

但是,当我们尝试使用它时,它会失败。

could not match type-parameter 0-1&& against (the actual parameter we tried to pass)

(单参考也失败了,但我测试以防万一)

有什么办法可以解决这个问题?是否可以在 C++11 中使用,或者仅具有后来 C++ 标准的功能?

std::forwardstd::ref或会std::cref以某种方式帮助吗?

这实际上更像是一个概念性问题,所以请原谅我所有这些代码都无法运行,要获得所有这些的可运行示例将是一项巨大的工作,我认为这个问题得到了很好的解释足以涵盖它,但如果需要澄清,请告诉我。

标签: c++c++11referencevariadic-templates

解决方案


&&不是引用,而是 r 值(可以是引用或值)。你应该使用Args& .... 我认为问题在于您正在使用template <class S, class... Args>应该使用template <class S, typename... Args>的地方,因为您的论点似乎并不总是类。

如果你想使用转发(std::vector.emplace_back例如),你可以使用Args && ...然后std::forward你的参数给函数。在您的情况下,您应该注意 usingstd::forward仍将通过值而不是通过引用传递您的参数。要实现通过 r 值传递参数,请std::move改用!


推荐阅读