什么是复制省略以及它如何优化复制和交换习惯用法?

时间:2010-01-27 00:37:01

标签: c++ optimization copy-and-swap copy-elision

我正在阅读Copy and Swap

我尝试阅读Copy Elision上的一些链接,但无法弄清楚它的含义。有人可以解释这个优化是什么,特别是下面的文字是什么意思

  

这不仅仅是为了方便而是实际上是一种优化。如果参数绑定到左值(另一个非常量对象),则在创建参数时会自动创建对象的副本。但是,当s绑定到rvalue(临时对象,文字)时,通常会省略该副本,从而保存对复制构造函数和析构函数的调用。在赋值运算符的早期版本中,参数被接受为const引用,当引用绑定到右值时,不会发生复制省略。这会导致创建和销毁其他对象。

2 个答案:

答案 0 :(得分:34)

存在复制构造函数以进行复制。理论上,当你写一行如:

CLASS c(foo());

编译器必须调用复制构造函数将foo()的返回值复制到c

复制省略是一种跳过调用复制构造函数的技术,以免支付开销。

例如,编译器可以安排foo()将其返回值直接构造为c

这是另一个例子。假设你有一个功能:

void doit(CLASS c);

如果使用实际参数调用它,编译器必须调用复制构造函数,以便不能修改原始参数:

CLASS c1;
doit(c1);

但是现在考虑一个不同的例子,假设你像这样调用你的函数:

doit(c1 + c1);

operator+将需要创建一个临时对象(一个右值)。编译器可以传递doit()创建的临时值,而不是在调用operator+之前调用复制构造函数,而是将其传递给doit()

答案 1 :(得分:2)

以下是一个例子:

#include <vector>
#include <climits>

class BigCounter {
 public:
   BigCounter &operator =(BigCounter b) {
      swap(b);
      return *this;
   }

   BigCounter next() const;

   void swap(BigCounter &b) {
      vals_.swap(b);
   }

 private:
   typedef ::std::vector<unsigned int> valvec_t;
   valvec_t vals_;
};

BigCounter BigCounter::next() const
{
   BigCounter newcounter(*this);
   unsigned int carry = 1;
   for (valvec_t::iterator i = newcounter.vals_.begin();
        carry > 0 && i != newcounter.vals_.end();
        ++i)
   {
      if (*i <= (UINT_MAX - carry)) {
         *i += carry;
      } else {
         *i += carry;
         carry = 1;
      }
   }
   if (carry > 0) {
      newcounter.vals_.push_back(carry);
   }
   return newcounter;
}

void someFunction()
{
    BigCounter loopcount;
    while (true) {
       loopcount = loopcount.next();
    }
}

somefunction loopcount = loopcount.next();BigCount::next()对复制省略有很大好处。如果不允许复制省略,那么该行将需要3次复制构造函数的调用以及对析构函数的相关调用。在允许复制省略的情况下,可以将复制构造函数调整为1,在newcounter内部显式调用operator =

如果BigCounter &BigCounter::operator =(const BigCounter &b) { BigCounter tmp(b); swap(tmp); return *this; } 已声明并定义如下:

newcounter

即使使用copy elision,也必须进行2次复制构造函数的调用。一个构建tmp,另一个构建operator =。如果没有复制省略,那么仍然会有3.这就是为什么声明{{1}}所以它的参数需要调用复制结构时,可以在为赋值运算符使用'复制和交换'惯用语时进行优化。当调用复制构造函数来构造一个参数时,它的调用可能会被省略,但是如果它被调用来创建一个局部变量,它可能不是。