3

我正在尝试制作一个类运行器(以固定的时间频率运行一个类),它在另一个线程中运行一个类,并且可以从主线程控制(如暂停、恢复、停止)。

所以我想利用 C++11 的 Functor 和其他特性。但是我有一个奇怪的问题,传递给 Runner 的 Functor 的析构函数被调用了两次。

#include <iostream>
#include <chrono>
#include <thread>

using namespace std;

class Runner {
 public:
  typedef function<bool()> fn_t;
  Runner(fn_t &&fn) : fn_(move(fn)), thread_(Thread, ref(*this)) {
    cout << "Runner" << endl;
  }
  ~Runner() {
    cout << "~Runner" << endl;
    thread_.join();
  }
 private:
  fn_t fn_;
  thread thread_;
  static void Thread(Runner &runner) {
    while (runner.fn_()) {
      cout << "Running" << endl;
      this_thread::sleep_for(chrono::milliumseconds(1));
    }
  }
};

class Fn {
 public:
  Fn() : count(0) {
    cout << "Fn" << endl;
  }
  ~Fn() {
    cout << "~Fn" << endl;
  }
  bool operator()() {
    return (++count < 5);
  }
 private:
  int count;
};

int main (int argc, char const* argv[])
{
  Fn fn;
  Runner runner(move(fn));
  return 0;
}

输出:

Fn
Runner
~Fn
~Runner
Running
Running
Running
Running
Running
~Fn
~Fn

如果我改变

Fn fn;
Runner runner(move(fn));

Runner runner(Fn());

该程序什么都没有,并且停滞不前。我试图禁用编译优化,没有任何变化。有什么解释吗?

我该如何解决这个问题或用其他方法做同样的事情?我应该像 std::async / std::thread 那样实现这个类吗?

更新到 Runner runner(Fn())

该语句作为函数声明被中断。

Runner runner((Fn()))解决了问题。

感谢所有评论和回答。在查看rvalue之后,似乎我误解了从地面 0 引用 rvalue 的含义。我会尝试其他一些方法。

此问题的最终解决方案

#include <iostream>
#include <chrono>
#include <thread>
#include <vector>

using namespace std;

template<typename T, typename... Args>
class Runner {
 public:
  Runner(Args&&... args) : 
      t(forward<Args>(args)...), 
      thread_(Thread, ref(*this)) {
    cout << "Runner" << endl;
  }
  ~Runner() {
    cout << "~Runner" << endl;
    thread_.join();
  }
 private:
  T t;
  thread thread_;
  static void Thread(Runner &runner) {
    while (runner.t()) {
      cout << "Running" << endl;
      this_thread::sleep_for(chrono::milliseconds(100));
    }
  }
};

class Fn {
 public:
  Fn() : count(0) {
    cout << "Fn" << endl;
  }
  ~Fn() {
    cout << "~Fn" << endl;
  }
  bool operator()() {
    return (count++ < 5);
  }
 private:
  int count;
};

int main (int argc, char const* argv[])
{
  //vector<Fn> fns;
  //fns.emplace_back(Fn());
  Runner<Fn> runner;
  return 0;
}

输出:

Fn
Runner
~Runner
Running
Running
Running
Running
Running
~Fn
4

2 回答 2

4

使用std::move

Runner(fn_t &&fn) : fn_(std::move(fn)), thread_(Thread, ref(*this)) {
    /*....*/
}

您需要显式使用std::move,否则它将被视为 const 引用。你也可以使用std::forward

Runner(fn_t &&fn) : fn_(std::forward<fn_t>(fn)), thread_(Thread, ref(*this)) {
    /*....*/
}
于 2012-05-16T19:12:54.967 回答
4

首先,您不应该在大多数情况下使用 r 值引用参数,除非在您自己的移动构造函数中。正如你所拥有的,没有办法将左值传递std::function<bool()>Runner.

int main()
{
    Fn fn;
    std::function<bool()> func(fn);
    Runner runner(func); // this is illegal
}

也许我只是没有足够的创造力,但我无法想象有什么正当理由要阻止这种事情发生。

你应该让std::function照顾它自己的复制/移动。当您需要对象的副本时,请按值获取参数。如果函数被传递一个 r 值,那么它将被移动构造。如果它被传递一个左值,那么它将被复制构造。然后,在您的Runner构造函数中,您可以将值移动到成员对象中,如 fontanini 所示。

但是,这些都不能保证减少析构函数的调用,因为当您移动一个对象时,您仍在创建第二个对象,并且必须销毁第二个对象。为了减少破坏,必须进行复制省略,这实际上确实避免了创建多个对象。但与搬家不同的是,这是一个实施问题,不能保证在您希望的所有情况下都生效。

于 2012-05-16T20:20:25.903 回答