【问题标题】:Interaction between std::move, return value optimization and destructorsstd::move、返回值优化和析构函数之间的交互
【发布时间】:2018-03-09 14:02:59
【问题描述】:

Stack Overflow 上有很多关于 std::move 和复制省略的交互的帖子,例如
What are copy elision and return value optimization?
How to be confident of copy elision / return-value optimization
c++11 Return value optimization or move?

但是,所有这些线程似乎都没有回答这个问题,即是否可以依靠编译器进行必要的优化。

如果在最坏的情况下我们的性能略有下降,这不是问题,但如果由于可能重复的析构函数调用而与正确性冲突,则可能是致命的。

我有以下情况:

class ObjectContainer {

    Object* obj;

public:

    ObjectContainer(Object* o): obj(o) {}
    ~ObjectContainer() {
        if (obj) delete obj;
    }

    ObjectContainer(ObjectContainer&& other): obj(other.obj) {
        other.obj = nullptr;    
    }

};

ObjectContainer create_container() {
    return ObjectContainer(new Object());
}

那里会发生什么?这能保证工作吗?或者是否存在代码无法编译的情况,或者更糟糕的是,析构函数在同一个对象上被调用两次?

据我了解,如果我声明了移动构造函数,编译器不会生成默认的复制构造函数。我必须指定一个复制构造函数吗?如果我这样做会怎样?还是应该在 return 语句中使用 std::move?

我将像这样调用上面的方法:

void do_stuff() {
    do_some_other_stuff(create_container());
}

void do_some_other_stuff(ObjectContainer const& oc) {
    ...
}

【问题讨论】:

  • 我相信很多答案都提到复制省略在 C++17 中是必需的,并且之前只允许使用。
  • 您违反了Rule of zero / 5,这将导致许多其他问题。如果你修复它,它也会使这种情况正常运行,而不会出现复制省略。
  • 感谢您的回答。在这种情况下,在不为 obj 添加引用计数器的情况下引入复制构造函数在语义上真的没有意义,是吗?我不知道 C++17 需要复制省略(我不记得在答案中的任何地方看到过该声明)。所以这意味着在 C++17 中,即使没有移动构造函数,这也很好?但是,我的代码也应该在 C++11 中工作。

标签: c++11 copy-elision return-value-optimization stdmove


【解决方案1】:

那里会发生什么?这能保证工作吗?或者是否存在代码无法编译的情况,或者更糟糕的是,析构函数在同一个对象上被调用了两次?

只要您使用自动变量和临时对象,C++ 就保证构造的每个对象都将被销毁。省略不会破坏代码的含义;这只是一个优化。

复制省略并没有真正省略复制/移动;它忽略了对象的存在。

在 C++17 之前的版本中,您的代码说要构造一个 ObjectContainer 临时地址,然后将其复制/移动到函数的 ObjectContainer 返回值中。编译器可以省略临时的创建,而是直接从临时的给定参数构造返回值对象。在这样做的过程中,它消除了复制/移动,但它也消除了它不会创建的临时对象的破坏。

(在C++17之后,这段代码说直接使用prvalue来初始化返回值对象,所以这里不可能使用临时的。)

我必须指定一个复制构造函数吗?

如果您希望对象是可复制的,您只需要指定一个复制构造函数。如果为类型提供移动构造函数,编译器将自动= delete 其他复制/移动构造函数/赋值运算符,除非您明确编写它们。

如果我这样做会怎样?

这一切都取决于您放入复制构造函数中的内容。如果您对值进行标准副本,那么您就破坏了您的类型的工作方式,因为它应该对分配的指针具有唯一的所有权。

或者我应该在 return 语句中使用 std::move 吗?

...你为什么要这样做? return 语句被赋予一个prvalue;你从不需要在prvalues上使用std::move

【讨论】:

  • 谢谢,我不确定我是否正确理解了该部分:“编译器可能会忽略临时的创建,[...]。这样做时,它会忽略复制/移动,但它也消除了对它不创造的暂时性的破坏。”因此,如果我没有指定移动构造函数,那么在没有复制省略的情况下,将创建一个临时对象(通过复制构造函数)并销毁(因此删除 obj)。另一方面,使用复制省略不会创建临时对象,因此不会删除 obj。那正确吗?如果不是,为什么?
  • @cero:移动构造函数的存在与否与省略无关(除非在省略对象时,此类特殊成员函数仍必须可调用)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-16
  • 1970-01-01
  • 2014-03-13
  • 1970-01-01
  • 2015-05-10
相关资源
最近更新 更多