繁体   English   中英

为什么拷贝构造函数没有在这里消失?

[英]Why isn't the copy constructor elided here?

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

取消副本构造函数似乎是一个直接的机会,因为访问foobar副本中的字段的值没有副作用。 但是复制构造函数调用,因为我得到了输出meep meep!

#include <iostream>

struct foo {
  foo(): a(5) { }
  foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
  int a;
};

struct bar {
  foo F() const { return f; }
  foo f;
};

int main()
{
  bar b;
  int a = b.F().a;
  return 0;
}

它不是12.8 / 15中描述的两个复制权删除法律案件:

返回值优化(从函数中返回自动变量,并通过直接在返回值中构造自动变量来避免将自动变量复制到返回值)-不。 f不是自动变量。

临时初始化程序(将临时文件复制到对象,而不是构造临时文件并将其复制,而是将临时值直接构建到目标中)-nope f也不是临时文件。 bF()是临时的,但不会被复制到任何地方,它只访问了一个数据成员,因此,当您离开F() ,没有什么可以逃脱的。

由于复制苹果的法律案例和将f复制到F()的返回值的法律案例均不会影响程序的可观察行为,因此该标准禁止忽略该程序。 如果您将打印替换为某些不可观察的活动,并检查了程序集,则可能会发现此副本构造函数已被优化。 但这将是在“假设”规则下,而不是在复制构造函数省略规则下。

仅当确实不需要复制时才进行复制省略。 特别是在执行一个函数的过程中存在一个对象(称为A),然后从第一个对象复制构造第二个对象(称为B),然后紧接着是A。将被销毁(即从函数退出时)。

在这种非常特殊的情况下,该标准允许编译器将A和B合并为两种引用同一对象的单独方法。 无需创建A,然后从A复制副本B,然后销毁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 ++均可从此代码中优化两个复制控制器。 即使关闭优化,g ++也会同时优化两者。 关闭优化功能后,VC ++会优化匿名返回,但是将复制ctor用于命名返回。

之所以调用复制构造函数,是因为a)无法保证您无需修改​​就可以复制字段值,并且b)因为复制构造函数有副作用(打印消息)。

考虑临时复制的一种更好的方法是使用临时对象。 这就是标准的描述方式。 如果将临时对象在即将销毁之前复制到永久对象中,则可以将其“折叠”成永久对象。

在这里,您可以在函数return中构造一个临时对象。 它实际上并没有参与任何事情,因此您希望它被跳过。 但是如果你做了

b.F().a = 5;

如果删除了副本,并且您对原始对象进行了操作,则可以通过非引用来修改b

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM