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

Здесь описано здесь . Основная реализация - в Objects/stringobject.c . Однако подклассы str, вероятно, не то, что вы хотите. Я бы предпочел здесь композицию; имеют объект со строковым полем и особым поведением.

30
задан Eight 31 May 2012 в 20:07
поделиться

2 ответа

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

CLASS c(foo());

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

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

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

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

void doit(CLASS c);

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

CLASS c1;
doit(c1);

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

doit(c1 + c1);

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

34
ответ дан 28 November 2019 в 00:16
поделиться

Вот пример:

#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' для оператора присваивания. Когда конструктор копирования вызывается для создания аргумента, его вызов может быть исключен, но если он вызывается для создания локальной переменной, то нет.

2
ответ дан 28 November 2019 в 00:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: