c++ - 使用 -fno-elide-constructors 编译时连续调用 move 构造函数

标签 c++ move rvo

在以下代码中(使用 -std=c++14 -Wall -fno-elide-constructors 在 gcc 9.2 上构建):


struct Noisy {
    Noisy() { std::cout << "Default construct [" << (void*)this << "]\n"; }
    Noisy(const Noisy&) { std::cout << "Copy construct [" << (void*)this << "]\n"; }
    Noisy(Noisy&&) { std::cout << "Move construct [" << (void*)this << "]\n"; }
    Noisy& operator=(const Noisy&) { std::cout << "Copy assignment" << std::endl; return *this; }
    Noisy& operator=(Noisy&&) { std::cout << "Move assignment" << std::endl; return *this; }
    ~Noisy() { std::cout << "Destructor [" << (void*)this << "]\n"; }
};

Noisy f() {
    Noisy x;
    return x;
}

Noisy g(Noisy y) {
    return y;
}
int main(void) {
    Noisy a;
    std::cout << "--- f() ---\n";
    Noisy b = f();
    std::cout << "b [" << (void*)&b << "]\n";
    std::cout << "--- g(a) ---\n";
    Noisy c = g(a);
    std::cout << "c [" << (void*)&c << "]\n";
    std::cout << "---\n";
    return 0;
}

这会产生这样的结果:

Default construct [0x7ffc4445737a]
--- f() ---
Default construct [0x7ffc4445735f]
Move construct [0x7ffc4445737c]
Destructor [0x7ffc4445735f]
Move construct [0x7ffc4445737b]
Destructor [0x7ffc4445737c]
b [0x7ffc4445737b]
--- g(a) ---
Copy construct [0x7ffc4445737e]
Move construct [0x7ffc4445737f]
Move construct [0x7ffc4445737d]
Destructor [0x7ffc4445737f]
Destructor [0x7ffc4445737e]
c [0x7ffc4445737d]
---
Destructor [0x7ffc4445737d]
Destructor [0x7ffc4445737b]
Destructor [0x7ffc4445737a]

为什么 f() 中的本地 Noisy 对象 [0x7ffc4445735f] 的拷贝在移入 f 后立即被破坏的返回地址(在 b 构造开始之前);而 g() 似乎没有发生同样的情况? IE。在后一种情况下(当 g() 执行时),函数参数 Noisy y, [0x7ffc4445737e] 的本地拷贝仅在c 已准备好构建。难道它不应该在被移入 g 的返回地址后立即被销毁,就像 f() 发生的情况一样吗?

最佳答案

这些是输出中地址的变量:

0x7ffc4445737a  a
0x7ffc4445735f  x
0x7ffc4445737c  return value of f() 
0x7ffc4445737b  b
0x7ffc4445737e  y
0x7ffc4445737f  return value of g()
0x7ffc4445737d  c

我将这个问题解释为:您强调以下两点:

  • x 在构造 b 之前被销毁
  • yc 构造后被销毁

并询问为什么两种情况的表现不同。


答案是:在 C++14 中,[expr.call]/4 中指定的标准是,当函数返回时,y 应该被销毁。然而,并没有明确指定函数返回的确切含义。提出了 CWG 问题。

从 C++17 开始,规范现在是实现定义的,y 是否与函数的局部变量同时销毁,或者在包含的完整表达式的末尾销毁函数调用。事实证明,这两种情况无法协调,因为这将是一个破坏性的 ABI 更改(想想如果 y 的析构函数抛出异常会发生什么); Itanium C++ ABI 还指定在完整表达式末尾进行销毁。

由于 C++14 措辞的歧义,我们不能明确地说 g++ -std=c++14 不符合 C++14,但无论如何由于 ABI 问题,现在不会更改。

有关标准和 CWG 报告链接的说明,请参阅此问题:Sequencing of function parameter destruction还有Late destruction of function parameters

关于c++ - 使用 -fno-elide-constructors 编译时连续调用 move 构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60717396/

相关文章:

c++ - 寻求将构建工具链从 bash/grep/sed/awk/(auto)make/configure 重新实现为更理智的东西(例如 boost.build 等)

c++ - 将整数传递给请求引用的函数

css - 页脚文字无法 move

Javascript更改div的位置并在应用动画后

c++ - 返回对象的移动构造函数会破坏 C++98 代码吗?

c++ - 有人可以解释这个 C++ 程序的输出吗?

c++ - 为什么 RVO 要求如此严格?

c++ - 只读操作的 std::map 线程安全

C++ 程序结束而不是重复 For 循环

javascript - JQuery - 获取 <p>太多溢出!</p> 的 .x .y 坐标,然后 move <div>