Что такое разрешение копирования и как оно оптимизирует идиому копирования и замены? - PullRequest
30 голосов
/ 27 января 2010

Я читал Копировать и поменять .

Я попытался прочитать некоторые ссылки на Copy Elision, но не смог понять, что это значит. Может кто-нибудь объяснить, что это за оптимизация, и, в частности, что означает следующий текст

Это не просто вопрос удобства, а фактически оптимизация. Если параметр (ы) привязывается к lvalue (другому неконстантному объекту), копия объекта создается автоматически при создании параметра (ов). Однако когда s связывается с rvalue (временным объектом, литералом), копия обычно удаляется, что сохраняет вызов конструктора копирования и деструктора. В более ранней версии оператора присваивания, где параметр принимался как константная ссылка, исключение копирования не происходит, когда ссылка связывается с значением r. В результате создается и уничтожается дополнительный объект.

Ответы [ 2 ]

34 голосов
/ 27 января 2010

Конструктор копирования существует для создания копий. Теоретически, когда вы пишете строку вроде:

CLASS c(foo());

Компилятор должен вызвать конструктор копирования, чтобы скопировать возвращаемый результат foo() в c.

Копирование elision - это метод, позволяющий пропустить вызов конструктора копирования, чтобы не платить за накладные расходы.

Например, компилятор может договориться о том, что foo() напрямую создаст свое возвращаемое значение в c.

Вот еще один пример. Допустим, у вас есть функция:

void doit(CLASS c);

Если вы вызываете его с фактическим аргументом, компилятор должен вызвать конструктор копирования, чтобы исходный параметр не мог быть изменен:

CLASS c1;
doit(c1);

Но теперь рассмотрим другой пример, скажем, вы вызываете свою функцию следующим образом:

doit(c1 + c1);

operator+ собирается создать временный объект (значение). Вместо вызова конструктора копирования перед вызовом doit(), компилятор может передать созданный operator+ временный файл и передать его doit().

2 голосов
/ 28 января 2010

Вот пример:

#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(); значительно выигрывает от копирования. Если исключение копирования не разрешено, для этой строки потребуется 3 вызова конструктора копирования и связанный вызов деструктора. С разрешенным разрешением копирования его можно уменьшить до 1 вызова конструктора копирования, явного внутри BigCount::next(), где объявлено newcounter.

Если operator = было объявлено и определено так:

BigCounter &BigCounter::operator =(const BigCounter &b) {
   BigCounter tmp(b);
   swap(tmp);
   return *this;
}

должно было быть 2 вызова конструктора копирования, даже с разрешением копирования. Один для построения newcounter, а другой для построения tmp. А без исключения копирования все равно было бы 3. Поэтому объявление operator =, так что его аргумент требует вызова конструкции копирования, может быть оптимизацией при использовании идиомы «copy and swap» для оператора присваивания. Когда конструктор копирования вызывается для создания аргумента, его вызов может быть исключен, но если он вызывается для создания локальной переменной, он может не быть.

...