Что такое копирование и как он оптимизирует идиому копирования и свопинга?

Я читал Копировать и своп.

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

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

Ответы

Ответ 1

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

CLASS c(foo());

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

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

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

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

void doit(CLASS c);

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

CLASS c1;
doit(c1);

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

doit(c1 + c1);

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

Ответ 3

Вот пример:

#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 вызова конструктора копирования, даже с копией elision. Один построить newcounter, а другой построить tmp. И без копирования, все равно будет 3. Что, почему объявление operator =, поэтому его аргумент требует вызова конструкции копии, может быть оптимизацией при использовании идиомы "копировать и заменять" для оператора присваивания. Когда конструктор копирования вызывается для построения аргумента, его вызов может быть отменен, но если он вызывается для создания локальной переменной, это может быть не так.