1

我无法找到以下问题的具体答案:

考虑以下代码:

Obj f() {
    Obj o2;
    return o2;
}

int main() {
    Obj o1 = f();
    return 0;
}

复制构造函数在没有编译器优化的情况下被激活了多少次?

如果没有移动构造函数,不是一次将o2复制到调用函数,另一次构造o1吗?

如果有move构造函数,不是一次将o2复制到调用函数,另一次构造o1(第二次是move const)吗?

4

2 回答 2

4

C++03 及之前

Obj被复制两次。一次通过return语句(构造返回值),一次o1通过复制返回值进行初始化。

C++11 和 C++14

如果Obj有一个可用的移动构造函数,它会被移动两次并被复制零次。该return语句必须使用移动,即使返回的表达式是左值。由于语言中的特殊规则,这种“移动优化”是强制性的;o2即使禁用了优化,也不得复制。o1初始化时发生第二次移动。

如果Obj没有移动构造函数或隐式删除的移动构造函数,则复制构造函数使用两次。

如果Obj有一个显式删除的移动构造函数,则程序是错误的,因为初始化o1尝试使用已删除的移动构造函数。

C++17 及更高版本

如果Obj有一个可用的移动构造函数,则在return执行语句时移动一次。如上所述,编译器必须使用移动而不是复制。的构造o1既不涉及复制,也不涉及移动。相反,initializes中的return语句不涉及临时性。这是因为“保证复制省略”:语言要求复制被省略,即使优化被禁用。这是因为是纯右值,并且纯右值不会具体化(即,实例化为临时对象),除非有必要这样做。标准创建的“法律虚构”实际上是返回创建的“配方”f()o1f()f()Obj,而不是Obj本身。在实践中,这可以像在标准的早期版本中实现(可选)返回值优化一样实现:调用者将指针o1直接传递给 into freturn语句构造Obj到这个指针中。

如果 的移动构造函数Obj被隐式删除或不存在,则该return语句将使用复制构造函数,因此将有一个副本和零个移动。

如果显式删除 的移动构造函数Obj,则程序格式错误,如 C++11/C++14 的情况。

在所有情况下

可以优化上述情况下的复制/移动。在涉及多个复制/移动操作的情况下,编译器可以优化其中任何一个或全部。

于 2021-01-30T21:19:08.760 回答
2

在 C++17 之前,是的,有两个复制构造函数调用(即使它们有副作用也可以省略)。-fno-elide-constructors您可以在 gcc/clang中看到这一点。

由于 C++17 由于新的临时实现规则,内部只涉及一个副本f(同样可以省略)。


准确地说,所有这些都是移动,而不是副本。

于 2021-01-30T20:45:07.327 回答