为什么复制构造函数不在这里?

时间:2010-04-26 22:26:03

标签: c++ optimization constructor

(我正在使用带有-O2的gcc。)

这似乎是一个忽略复制构造函数的直接机会,因为访问bar的{​​{1}}副本中的字段值没有副作用;但是复制构造函数调用,因为我得到了输出foo

meep meep!

4 个答案:

答案 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