Что такое копирование и как он оптимизирует идиому копирования и свопинга?
Я читал Копировать и своп.
Я пробовал читать некоторые ссылки на 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()
.
Ответ 2
См. http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/, в котором объясняется копия elision (и оптимизация возвращаемого значения) и приведены некоторые примеры.
Ответ 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 =
, поэтому его аргумент требует вызова конструкции копии, может быть оптимизацией при использовании идиомы "копировать и заменять" для оператора присваивания. Когда конструктор копирования вызывается для построения аргумента, его вызов может быть отменен, но если он вызывается для создания локальной переменной, это может быть не так.