为什么禁用非强制复制消除会导致 C++17 之前和之后不同的行为

问题描述 投票:0回答:1

我一直在使用不同的编译器标志和版本尝试以下 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++ c++11 c++17 copy-elision nrvo
1个回答
0
投票

因此,首先,当您说“在 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

© www.soinside.com 2019 - 2024. All rights reserved.