C++11中rvalue调用析构函数两次

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));

to

Runner runner(Fn());

程序没有输出,且停滞不前。我尝试禁用编译优化,但结果没有变化。有何解释?
如何修复此问题或通过其他方法实现相同功能?应该像std::async / std::thread一样实现这个类吗?
更新为Runner runner(Fn()) 此语句被中断,当作函数声明。 Runner runner((Fn()))解决了这个问题。
感谢所有评论和答案。阅读了rvalue后,似乎我从头就误解了rvalue reference的含义。我将尝试其他方法。
此问题的最终解决方案。
#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

6
添加 Fn(Fn const&) { cout << "Fn" << endl; } 你就会看到。 - R. Martinho Fernandes
1
它不使用std::move和rvalue应该避免复制吗? - xiaoyi
问题在于,如果Functor持有某些资源(例如char*),并且在析构时会自动释放,那么复制和析构将在复制的Functor中留下无效的内存指针。如何解决这个问题? - xiaoyi
1
@xiaoyi 如果Functor持有必须被释放的资源,它还应该定义一个复制构造函数(和赋值运算符)来管理该资源。这解决了你的问题,除了多了一次复制操作。 - Praetorian
2
正如所说的那样,在移动操作之后d-tor被调用两次并没有什么问题(尽管我在你的输出中看到了三个~Fn)。如果支持移动语义,您的类应该处理它。对于STL中的类,保证在移动后对象处于有效但未指定的状态(更多信息请参见:https://dev59.com/KWsz5IYBdhLWcg3wZm3Q)。 - marcinj
显示剩余4条评论
2个回答

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

你需要明确使用 std::move,否则它将被视为常量引用。你也可以使用 std::forward

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

4

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

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

也许我只是不够有创意,但我想象不出任何有效的理由来阻止这样做。
应当让std::function自己处理它的复制和移动。当需要一个对象的副本时,按值传递参数。如果函数传递的是右值,则会进行移动构造。如果传递的是左值,则会进行复制构造。然后,在Runner构造函数中,您可以将该值移动到成员对象中,就像fontanini所示。
但这些都不能保证减少析构调用,因为当您移动对象时,仍会创建第二个对象,并且必须销毁第二个对象。为了看到更少的破坏,必须发生复制省略,这实际上避免了创建多个对象。但与移动不同,这是一种实现问题,不能保证在所有希望发生的情况下都会发生。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接