(我正在使用带有-O2
的gcc。)
这似乎是一个忽略复制构造函数的直接机会,因为访问bar
的{{1}}副本中的字段值没有副作用;但是复制构造函数被调用,因为我得到了输出foo
。
meep meep!
答案 0 :(得分:11)
这不是12.8 / 15中描述的复制文件的两个法律案例:
返回值优化(从函数返回自动变量,并通过直接在返回值中构造自动来省略将该自动复制到返回值) - nope。 f
不是自动变量。
临时初始化程序(临时复制到一个对象,而不是构造临时和复制它,临时值直接构造到目标) - nope f
也不是临时的。 b.F()
是一个临时的,但它不会被复制到任何地方,它只是访问了一个数据成员,所以当你离开F()
时,没有什么可以忽略的。
由于这两个法律案件都没有,f
复制到F()
的返回值会影响该程序的可观察行为,因此该标准禁止将其删除。如果您使用一些不可观察的活动替换了打印,并检查了程序集,您可能会看到此复制构造函数已经过优化。但这将属于“as-if”规则,而不是复制构造函数elision规则。
答案 1 :(得分:2)
只有在不需要副本时才会发生复制省略。特别是,当一个对象(称为A)在执行函数期间存在时,以及第二个对象(称为B)将从第一个对象复制构造,并且立即< / em>之后,A将被销毁(即退出函数时)。
在这种非常具体的情况下,标准允许编译器将A和B合并为两种不同的引用同一对象的方式。而不是要求创建A,然后B是从A构造的复制,然后A被销毁,它允许A和B被认为是引用同一对象的两种方式,因此(一个)对象被创建为A,并且在函数返回之后开始被称为B,但即使复制构造函数具有副作用,仍然可以跳过从A创建B的副本。此外,请注意,在这种情况下,A(作为与B分开的对象)也不会被破坏 - 例如,如果你的dtor也有副作用,他们也可以(也)被省略。
您的代码不适合该模式 - 第一个对象在用于初始化第二个对象后不会立即停止存在。 F()
返回后,该对象有两个个实例。在这种情况下,[命名]返回值优化(又称复制省略)根本不适用。
复制省略适用时的演示代码:
#include <iostream>
struct foo {
foo(): a(5) { }
foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
int a;
};
int F() {
// RVO
std::cout << "F\n";
return foo();
}
int G() {
// NRVO
std::cout << "G\n";
foo x;
return x;
}
int main() {
foo a = F();
foo b = G();
return 0;
}
MS VC ++和g ++都在优化开启时从这段代码中优化了两个拷贝ctors。即使关闭优化,g ++也会优化两者。关闭优化后,VC ++会优化匿名返回,但使用copy ctor进行命名返回。
答案 2 :(得分:1)
调用复制构造函数是因为a)无法保证您在不修改的情况下复制字段值,并且b)因为您的复制构造函数具有副作用(打印消息)。
答案 3 :(得分:1)
考虑复制省略的更好方法是临时对象。这就是标准描述它的方式。如果临时对象在被销毁之前被复制到永久对象中,则允许将其“折叠”为永久对象。
在这里,您可以在函数return中构造一个临时对象。它并没有真正参与任何事情,所以你希望它被跳过。但是如果你做了怎么办
b.F().a = 5;
如果副本被删除,并且您对原始对象进行了操作,则可以通过非引用修改b
。