我一直在使用不同的编译器标志和版本尝试以下 C++ 代码,观察
C++11
和 C++17
之间对象构造的不同行为。
我注意到,随着
-fno-elide-constructors
和 C++17
中的 C++11
标志,NRVO
在函数 f
中被禁用。然而,在C++17中,对象t1
是直接在t2
的位置创建的,但在C++11中,这一步并没有被消除。我想知道为什么在 C++17 中,尽管禁用了该标志,但考虑到这不是强制复制省略的情况,这一步仍然被消除了!
我还查了很多资料和那些著名的问题,但不明白为什么!
她也是代码链接: https://godbolt.org/z/PYfejba6M
#include <iostream>
struct Thing
{
Thing() { std::cout << "Constructor\n"; }
~Thing() { std::cout << "Destructor\n"; }
Thing(const Thing&) { std::cout << "Copy Constructor\n"; }
Thing& operator=(const Thing&) { std::cout << "Copy Assignment Operator\n"; return *this; }
Thing(Thing&&) noexcept { std::cout << "Move Constructor\n"; }
Thing& operator=(Thing&&) noexcept { std::cout << "Move Assignment Operator\n"; return *this; }
};
Thing f()
{
std::cout << "Entering f()\n";
Thing t1;
std::cout << "Thing t1 constructed in f()\n";
std::cout << "Preparing to return t1 from f()\n";
return t1;
}
int main()
{
std::cout << "Entering main()\n";
[[maybe_unused]] Thing t2 = f();
std::cout << "t2 object returned and assigned in main()\n";
std::cout << "Exiting main()\n";
}
因此,首先,当您说“在 C++17 中,对象
t1
是直接在 t2
的位置创建的”时,您并没有正确描述代码的行为。在您自己的 godbolt 链接中,使用 -std=c++17 -fno-elide-constructors
,您会注意到它仍然显示一个 Move constructor
输出和一对 Destructor
输出。 t1
不是 直接在t2
的位置创建。使用 -std=c++11 -fno-elide-constructors
,您将获得 two Move constructor
输出,因此 C++17 即使使用标志也会删除两个移动中的 one,但不会同时删除两者。
原因是
-fno-elide-constructors
禁用了可选复制省略。 NRVO 在这两个标准中都是可选的,因此无论 C++ 标准如何,事实上,当您在 return t1;
中执行 f
时,它确实会导致发生一次移动,从函数内部的 t1
移动到“暂存区域” " 为返回值。
在 C++11 中,这个“暂存区域”与您分配给的
t2
是分开的;从“暂存区域”到 t2
的复制省略是非强制的(因为 C++11 缺乏强制的复制省略)并且被 -fno-elide-constructors
禁用,所以你最终会移动两次,一次从 t1
到停靠区,一次从停靠区到t2
。
根据合同,在 C++17 及更高版本中,从“暂存区域”到
t2
的复制是强制性的。因此,虽然 -fno-elide-constructors
禁用从 t1
到“暂存区域”的非强制复制省略,但“暂存区域”本身是 required 为 t2
本身(因为 Thing t2 = f();
需要省略),所以仅发生一次移动,直接从 t1
到t2
。