偶然发现几篇文章声称,如果函数仍然可以复制,按值传递可以提高性能。
我从未真正想到过如何在后台实现值(value)传递。当您执行如下操作时,堆栈上到底发生了什么:F v = f(g(h()))?
经过深思熟虑后,我得出的结论是我将以实现g()返回的值的方式在f()期望的位置创建它的方式来实现它。因此,基本上,没有复制/移动构造函数调用-f()将简单地获取g()返回的对象的所有权,并在执行离开f()的范围时销毁它。与g()相同-它将获取h()返回的对象的所有权并在返回时销毁它。
las,编译器似乎不同意。这是测试代码:
#include <cstdio>
using std::printf;
struct H
{
H() { printf("H ctor\n"); }
~H() { printf("H dtor\n"); }
H(H const&) {}
// H(H&&) {}
// H(H const&) = default;
// H(H&&) = default;
};
H h() { return H(); }
struct G
{
G() { printf("G ctor\n"); }
~G() { printf("G dtor\n"); }
G(G const&) {}
// G(G&&) {}
// G(G const&) = default;
// G(G&&) = default;
};
G g(H) { return G(); }
struct F
{
F() { printf("F ctor\n"); }
~F() { printf("F dtor\n"); }
};
F f(G) { return F(); }
int main()
{
F v = f(g(h()));
return 0;
}
在MSVC 2015上,其输出正是我所期望的:
H ctor
G ctor
H dtor
F ctor
G dtor
F dtor
但是,如果您将副本构造函数注释掉,它看起来像这样:
H ctor
G ctor
H dtor
F ctor
G dtor
G dtor
H dtor
F dtor
我怀疑删除用户提供的副本构造函数会导致编译器生成move-constructor,这反过来会导致不必要的“移动”,无论有多大的对象都不会消失(尝试添加1MB数组作为成员变量)。即编译器非常喜欢“移动”,因此选择它而不是根本不做任何事情。
这似乎是MSVC中的错误,但是我真的很希望有人能解释(和/或证明)这里发生的事情。这是第一个问题。
现在,如果您尝试使用GCC 5.4.0,则输出毫无意义:
H ctor
G ctor
F ctor
G dtor
H dtor
F dtor
必须在创建F之前将H销毁! H对于g()的作用域是局部的!请注意,此处使用构造函数对GCC的影响为零。
与MSVC相同-对我来说似乎是个错误,但是有人可以解释/证明这里发生了什么吗?那就是问题2。
真是愚蠢的是,经过多年与C++的专业合作,我遇到了这样的问题……经过近四十年的时间,编译器仍然不能就如何传递值达成共识?
最佳答案
为了通过值传递参数,参数是函数的局部变量,并且已从相应的参数初始化为函数调用。
按值返回时,有一个值称为返回值。这由return
表达式的“参数”初始化。它的生存期一直到包含函数调用的完整表达式结束为止。
还有一种称为copy elision的优化可以在少数情况下应用。其中两种情况适用于按值(value)返回:
这两种可能同时应用。同样,从C++ 14开始,复制省略对于编译器是可选的。
在调用
f(g(h()))
中,这是对象列表(不带复制省略):H
默认由return H();
构造H
的返回值h()
从(步骤1)开始复制构造。 ~H
(步骤1)H
的参数g
从(步骤2)开始复制构造。 G
默认由return G();
构造G
从(第5步)开始复制构造。 g()
(第5步)~G
(第4步)(参见下文)~H
的参数G
从(步骤6)开始复制构造。 f
默认由F
构造return F();
的返回值F
从(步骤10)开始移动构造。 f()
(步骤10)~F
(第9步)(参见下文)~G
从(步骤11)开始移动构建。 F v
,~F
,~G
(步骤2、6、11)已销毁-我认为这三个~H
(步骤14)对于复制省略,可以将步骤1 + 2 + 3合并为“默认构造
~F
的返回值”。对于5 + 6 + 7和10 + 11 + 12同样。但是,也可以将2 + 4单独组合为“h()
的参数是从1复制构造的”,并且还可以同时应用这两个元素,给出“g
的参数是默认构造的”。由于复制省略是可选的,因此您可能会从不同的编译器看到不同的结果。这并不意味着存在编译器错误。您会很高兴听到在C++ 17中某些复制省略方案被强制执行。
如果您包含move-constructor的输出文本,则在第二种MSVC情况下的输出将更具指导性。我猜想在第一种MSVC情况下,它同时执行了我上面提到的两个省略,而第二种情况则省略了“2 + 4”和“6 + 9”省略。
之下的:gcc和clang延迟破坏函数参数,直到封闭函数调用的完整表达式结束。这就是为什么您的gcc输出与MSVC不同的原因。
在C++ 17的起草过程中,这些破坏是发生在我将其包含在列表中的位置,还是出现在完整表达式的末尾,由实现定义。可以争辩说,在较早发布的标准中对它的定义不够充分。 See here有待进一步讨论。
关于c++ - 通过值传递参数时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39803711/