在以下代码中(使用-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
的构建开始之前(立即被破坏;而CCD_ 6似乎没有发生同样的情况?即,在后一种情况下(当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
之前被破坏c
构建后y
被破坏
并询问为什么两种情况的表现不一样。
答案是:在C++14中,[expr.call]/4中指定的标准是,当函数返回时,y
应该被销毁。然而,并没有明确说明这意味着函数返回的确切阶段。有人提出了CWG问题。
从C++17开始,现在的规范是,它的实现定义了y
是在与函数的局部变量同时销毁,还是在包含函数调用的完整表达式结束时销毁。事实证明,这两种情况无法调和,因为这将是一个破坏性的ABI更改(想想如果y
的析构函数抛出异常会发生什么(;而且安腾C++ABI指定在完整表达式的末尾进行销毁。
由于C++14措辞的模糊性,我们不能确切地说g++ -std=c++14
不符合C++14,但无论如何,由于ABI问题,它现在不会改变。
有关标准和CWG报告链接的解释,请参阅此问题:功能参数破坏的排序以及功能参数的后期破坏
如果您查看生成的程序集(例如在编译器资源管理器上(,则差异非常明显。
在这里,您可以看到,对于g
的调用,参数对象实际上是在main
函数中创建和销毁的。
因此,对于g
函数,输出顺序为
- 从
a
复制参数y
的构造 - 调用函数
g
,传递y
- 在函数
g
中,y
被移动到临时返回对象中 - 函数
g
返回 - 回到
main
,临时返回对象移动到c
- 临时返回对象已销毁
- 参数对象
y
已销毁
对于函数f
,本地对象x
在f
:的范围内构造和销毁
f
被调用- 默认构造
x
- 临时返回对象是由
x
构造的move x
被破坏- 函数
f
返回 - 临时返回对象移动到
b
- 临时返回对象已销毁