使用-fno-ellide-constructors进行编译时连续调用移动构造函数

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

在以下代码中(使用-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]

为什么[0x7ffc4445735f]中的本地噪声对象f()的副本在移入f的返回地址后(以及b的构造开始之前)立即被销毁;而g()似乎没有发生同样的事情吗?即在后一种情况下(执行g()时),只有在准备好构造好Noisy y之后,函数参数[0x7ffc4445737e]c的本地副本才会被销毁。它不应该在移到g的寄信人地址后立即被销毁吗?

c++ move rvo
1个回答
1
投票

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

f()

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

  • [0x7ffc4445737a a 0x7ffc4445735f x 0x7ffc4445737c return value of f() 0x7ffc4445737b b 0x7ffc4445737e y 0x7ffc4445737f return value of g() 0x7ffc4445737d c 在构建x之前被销毁
  • [b构建完成后销毁]

并询问为什么两种情况的表现都不一样。


答案是:在C ++ 14中,在[expr.call] / 4中指定的标准是在函数返回时应该销毁y。但是,有些编译器不符合此要求,因此提出了缺陷报告。从C ++ 17开始,现在规范是由实现定义的,是在函数返回时还是在包含函数调用的完整表达式的末尾销毁c

您的编译器采用后一种选项。作为缺陷,这意味着可以考虑追溯应用,因此即使使用y,编译器也可以正确应用缺陷的解决方案。

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