有没有一种方法可以模拟C ++中的破坏性移动?

时间:2018-11-29 13:33:21

标签: c++ c++11 destructor std-function move-constructor

我想避免破坏销毁了对象的对象的开销。  例如,我有一个这样的课程:

#include <iostream>
#include <functional>

class TestClass {
 public:
  explicit TestClass(std::function<void(void)> f) : f_(std::move(f)) {}
  TestClass(const TestClass &) = delete;
  TestClass(TestClass &&testobj) {
    f_ = testobj.f_;
    testobj.f_ = default_f_;
  }
  ~TestClass() { f_(); }
 private:
  static std::function<void(void)> default_f_;
  std::function<void(void)> f_;
};

std::function<void(void)> TestClass::default_f_ = [] { std::cout << "do nothing\n"; };

int main() {
  std::function<void(void)> f = [] { std::cout << "do something\n"; };
  TestClass t1(f);
  TestClass t2(std::move(t1));
}

在这种情况下,我将t1移到t2中。我必须创建default_f_,以便在从t1移动之后,可以使用它将f_设置为有效对象,这样销毁就不会引发试图调用空{{ 1}}。

是否有某种方法可以避免使用std::function,因为这样做似乎效率很低?

编辑:
很多人建议添加支票以避免这种情况。

是的,我在编写的每个析构函数中都这样做了,这只是一个例子。我认为,当发生move构造时,通常认为旧对象是无用的,并且编译器知道这一点,因此即使在析构函数中进行检查也是额外的有效负载,因为我们确定if语句始终是一个在这种情况下一定的价值。

我发现一则帖子也认为析构函数也可能成为瓶颈:https://www.meetingcpp.com/blog/items/c1y-move-copy-destructors.html。这篇文章写在愚人节那天,是个玩笑,但总的想法是我想要的。

正如Peter所指出的,我找到了一篇帖子,谈论这个“破坏性举动”一词:https://foonathan.net/blog/2017/09/14/destructive-move.html。我同意,当发生移动分配时,旧对象是无效对象,不应访问。

1 个答案:

答案 0 :(得分:3)

std::function的实现方式使其在调用operator()时必须具有有效可调用对象的位置。从std::function移动时,剩下的是有效但未指定的对象,该对象不再可调用。由于要在析构函数中调用函数,因此有两个选择。您可以将f_设置为像您所做的那样不执行任何操作

class TestClass {
public:
    explicit TestClass(std::function<void(void)> f) : f_(std::move(f)) {}
    TestClass(const TestClass &) = delete;
    TestClass(TestClass &&testobj) f_(std::move(testobj.f_)) { f_ = [](){}; }
    ~TestClass() { f_(); }
private:
    std::function<void(void)> f_;
};

或者您可以使用if语句解决此问题。您检查_f是否可调用,并且只有在可以调用时才能在析构函数中调用它。这样您就可以将代码编写为

class TestClass {
public:
    explicit TestClass(std::function<void(void)> f) : f_(std::move(f)) {}
    TestClass(const TestClass &) = delete;
    TestClass(TestClass &&testobj) = default;
    ~TestClass() { if (f_) f_(); }
private:
    std::function<void(void)> f_;
};

这两个选项都将摆脱default_f_,但是您仍然需要做一些工作以确保您有一个有效的对象,或者在调用f_之前检查是否有一个有效的对象。