课堂建设的额外副本

时间:2014-06-13 16:44:13

标签: c++ constructor copy

在C ++中,初始化类

MyClass(myBigObject s):
  s_(s)
{
...
}

看起来好像s在功能输入时复制一次("按值传递#34;),一次被分配到s_

编译器是否足够智能去除第一个副本?

3 个答案:

答案 0 :(得分:3)

允许编译器删除第一个副本iff

  1. 根据as-if规则,副本没有可观察的行为 要应用此规则,在编译myBigObject ctor的用户时,编译器必须知道MyClass的构造函数或析构函数都没有任何可观察的副作用。

      

    1.9程序执行

         

    本国际标准中的语义描述定义了参数化的非确定性摘要   机。本国际标准对符合实施的结构没有要求。   特别是,它们不需要复制或模拟抽象机器的结构。相反,符合   如下所述,实现需要模拟(仅)抽象机器的可观察行为   下方。

  2.   
  3. 或者他们是否可以使用copy-elision规则,该规则允许忽略as-if规则   要应用此选项,必须为构造函数提供匿名的未绑定对象。

         

    12.8复制和移动类对象§31

         

    当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间。在没有优化的情况下销毁.123在下列情况下允许复制/移动操作的省略,称为复制省略(可以合并以消除多份副本):
       - 在具有类返回类型的函数的return语句中,当表达式是a的名称时   具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外),通过将自动对象直接构造为函数的返回值,可以省略复制/移动操作    - 在throw-expression中,当操作数是非易失性自动对象的名称(函数或catch子句参数除外),其范围不会超出最内层的末尾   封闭try-block(如果有的话),从操作数到异常的复制/移动操作   通过将自动对象直接构造到异常对象
    中,可以省略对象(15.1)    - 当复制/移动未绑定到引用(12.2)的临时类对象时   对于具有相同cv-unqualified类型的类对象,可以省略复制/移动操作   将临时对象直接构造到省略的复制/移动的目标中    - 当异常处理程序的异常声明(第15条)声明一个相同类型的对象(cv-qualification除外)作为异常对象(15.1)时,可以通过处理异常声明来省略复制/移动操作作为异常对象的别名,如果程序的含义将保持不变,除了执行异常声明声明的对象的构造函数和析构函数。

  4.      通过移动引用&&更容易获取参数,它允许在任何情况下用移动替换第二个副本。

答案 1 :(得分:1)

在一般情况下,

编译器无法优化从参数到calss字段的复制。显然,您的对象可以具有复杂的复制构造函数,其副作用无法忽略。

但在您的情况下,您可能希望将复制替换为moving

您应该编写移动构造函数并使用std::moves移至_s

myBigObject(myBigObject&&other);

MyClass(myBigObject s):
  s_(std::move(s))
{
...
}

在这种情况下代码如

MyClass obj((myBigObject()));

将以零拷贝结束,因为对象将首先移动到构造函数,然后移动到类字段。

答案 2 :(得分:1)

对于临时

,可以省略第一次复制到s

如果传递临时对象,优化编译器可能会省略第一个副本:

MyClass x{ myBigObject() };

这可能只调用一次复制构造函数,因为临时myBigObject将直接构造到构造函数参数s中。

请注意,这可能会改变程序的可观察行为。

#include <iostream>

struct myBigObject
{
  size_t x;
  myBigObject() : x() {}
  myBigObject(myBigObject && other)
  {
    std::cout << "Move myBigObject" << std::endl;
  }
  myBigObject(const myBigObject &other)
  {
    std::cout << "Copy myBigObject" << std::endl;
    x = 12;
  }
};

struct MyClass
{
    MyClass(myBigObject s)
      : s_(s) 
    { 
      std::cout << "x of s : " << s.x << std::endl;
      std::cout << "x of s_ : " << s_.x << std::endl;
    }
    myBigObject s_;
};

int main()
{
  std::cout << "A:" << std::endl;
  MyClass x{ myBigObject() };
  std::cout << "B:" << std::endl;
  myBigObject y;
  MyClass z{ y };
}

打印(https://ideone.com/hMEv1W + MSVS2013,Toolsetv120)

A:
Copy myBigObject
x of s : 0
x of s_ : 12
B:
Copy myBigObject
Copy myBigObject
x of s : 12
x of s_ : 12

无法省略第二次复制到s_

由于s_s需要是不同的对象,因此无法省略s_的副本。

如果您只想在课程中使用myBigObject的一个副本,则可以选择:

MyClass(myBigObject const & s)
  : s_(s) 
{ 
}
MyClass(myBigObject && s)
  : s_(std::forward<myBigObject>(s))
{
}

这样,在临时对象和非临时对象只有一个副本的情况下,您看不到任何副本。

更改的代码将打印:

A:
Move myBigObject
B:
Copy myBigObject