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

Я читал Copy и Swap.

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

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

2 ответа

Решение

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

CLASS c(foo());

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

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

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

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

void doit(CLASS c);

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

CLASS c1;
doit(c1);

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

doit(c1 + c1);

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

Вот пример:

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

Другие вопросы по тегам