2010-05-19 5 views
9

Я искал, как реализовать оператор + правильно по всему интернету, и все результаты я нашел сделать следующие шаги:Кто удаляет скопированный экземпляр в + оператор? (С ++)

const MyClass MyClass::operator+(const MyClass &other) const 
{ 
    MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this); 
    result += other;   // Use += to add other to the copy. 
    return result;   // All done! 
} 

У меня есть несколько вопросов по поводу этого «процесса»:

  1. Не так ли глупо реализовать этот оператор +, он вызывает оператор присваивания (который копирует класс) в первой строке, а затем конструктор копирования в возврате (который также копирует класс из-за того, что return по значению, поэтому он уничтожает первую копию и создает новую .., которая, откровенно говоря, не очень умна ...)

  2. Когда я пишу a = b + c, часть b + c создает новую копию класса, тогда часть 'a =' копирует копию самому себе. , который удаляет копию, созданную b + c?

  3. Есть ли лучший способ реализовать + оператор, не справляясь с классом дважды, а также без проблем с памятью?

заранее спасибо

+3

В касательной, вы можете посыпать некоторые заявления COUT в классе конструкторов, конструктор копирования, оператор присваивания и деструктор, затем выполнить 'а = Ь + с; '. Получающийся результат может удивить вас, и он довольно образован, когда вы впервые изучаете C++. –

+3

@OopsUser, вам нужно обратить более пристальное внимание на разницу между * удаляет * и * уничтожает *. В вашем вопросе переменная 'result' представляет собой переменную с автосогласованием, которая живет в стеке. Он выходит из области действия, когда функция возвращается и * уничтожается * как часть автоматической очистки функции. Это, однако, не удалено *. –

+1

Jon Skeet удаляет его. –

ответ

5

в этих условиях, я бы, вероятно, рассмотреть что-то вроде:.

MyClass MyClass::operator+(MyClass other) { 
    other += *this; 
    return other; 
} 

Дэйв Абрахамс написал сообщение article, объясняя, как это работает, и почему этот вид кода обычно достаточно эффективен, хотя изначально кажется, что этого не должно быть.

Редактировать (спасибо MSalters): Да, это предполагает/зависит от сохранения коммутационных свойств для MyClass. Если a+b != b+a, то исходный код - это то, что вы хотите (в большинстве случаев применяются те же рассуждения).

+3

Обратите внимание, что это дает неожиданные результаты для строк. 'MyString (" a ") + MyString (" b ") == MyString (" ba ")'! Он работает только тогда, когда a + b == b + a – MSalters

+0

Спасибо за ссылку на статью; в последующих статьях в серии есть действительно четкое описание ссылок rvalue и семантики перемещения. –

+3

Просто используйте свободную функцию вместо члена (передавая первый аргумент по значению, а второй - const ref), который опускает проблему коммутации, улучшает инкапсуляцию класса и имеет такое же использование. –

6
  1. Это эффективно не оператор присваивания, но конструктор копирования. Операция типа добавления создает новое значение, в конце концов, поэтому оно должно быть создано где-то. Это более эффективно, чем кажется, поскольку компилятор может свободно выполнять Оптимизацию возвращаемых значений, что означает, что он может построить значение непосредственно там, где он будет использоваться.

  2. result объявлен как локальная переменная и, следовательно, уходит с вызовом функции - кроме случаев, когда используется RVO (см. Выше), и в этом случае он никогда не был создан в функции, а в вызывающем.

  3. Не совсем; этот метод намного эффективнее, чем в первую очередь.

+0

Привет, Дэвид О ответе № 2, я не совсем понял ваш ответ, скажем, что никакой оптимизации не используется, где точно создана копия b + c? , локальная переменная которой оператор? вы можете более подробно объяснить, где именно возвращаемое значение сидит, когда оператор + уже завершен, а оператор = еще не запущен ... спасибо – OopsUser

+0

Возвращаемое значение 'operator + (b, c)' is временный. Он разрушается так же, как любое другое значение, возвращаемое функцией, то есть в конце полного выражение. – MSalters

1

Проблемы с памятью (при условии, что оператор присваивания и конструктор копирования хорошо написаны). Просто потому, что вся память для этих объектов берется в стеке и управляется компилятором. Кроме того, компиляторы оптимизируют это и выполняют все операции непосредственно на финале a вместо копирования в два раза.

1
  1. Это правильный способ реализации оператора + в C++. Большинство копий, которые вы так боитесь, будут устранены компилятором и будут подвергнуты переносу семантики в C++ 0x.

  2. Класс является временным и будет удален. Если вы привязываете временное значение к const&, срок службы временного продления будет увеличен до срока службы ссылки на константу.

  3. Может ли реализация его как свободной функции несколько более очевидна. Первый параметр в MyClass :: operator + является неявным, и компилятор будет переписывать функцию в operator + (const MyClass &, const MyClass &) в любом случае.

3

Это, по-видимому, правильный способ реализации operator+. Несколько точек:

  • MyClass result = *this не использует оператор присваивания, следует вызвать конструктор копирования, как если бы оно было написано MyClass result(*this).
  • Возвращаемое значение, когда оно используется в a = b + c, называется временным, и компилятор отвечает за его удаление (что, вероятно, произойдет в конце инструкции, т.е. точки с запятой, после того, как все остальное сделано). Вам не нужно беспокоиться об этом, компилятор всегда будет очищать временные ряды.
  • Нет лучшего способа, вам нужна копия. Однако компилятору разрешено оптимизировать временные копии, поэтому не так много, как вы думаете. Однако в C++ 0x вы можете использовать конструкторы перемещения , чтобы улучшить производительность, передав владение содержимым временного, а не копируя его по своему свойству.
3

он вызывает оператор присваивания (который копирует класс) в первой строке

Нет, это копия инициализация (через конструктор).

затем конструктор копирования в возврате (который также копирует класс

Составители может (и, как правило, делают) игнорировать эту копию, используя NRVO.

Когда я пишу = Ь + c, элемент b + c создает новую копию класса, затем часть «a =» копирует копию самому себе, который удаляет копию, созданную b + c

Компилятор, как и любое другое временное значение. Они будут удалены в конце полного выражения (в данном случае, это означает, что во время или после ; в конце линии.

Есть ли лучший способ реализации оператора + не справляясь классом дважды, а также ?., без каких-либо проблем с памятью

не на самом деле это не то, что неэффективное

2

Я буду стараться изо всех ответить:

Point (1): Нет, это не вызывает оператор присваивания. Вместо этого он вызывает конструктор. Так как вам все равно нужно построить объект (поскольку operator+ возвращает копию), это не приводит к дополнительным операциям.

Точка (2): Временная result создана в стеке и, следовательно, не создает проблемы с памятью (она уничтожается при выходе из функции). В поле return создается временное создание так, что назначение (или конструктор копирования) может использоваться для присвоения результатов aa=b+c;) даже после того, как result уничтожен. Это временное время автоматически уничтожается компилятором.

Пункт (3): Вышеупомянутый стандарт предписывает. Помните, что разработчикам компилятора разрешено оптимизировать реализацию, если эффект такой же, как и в стандарте. Я считаю, что компиляторы на самом деле оптимизируют многие из копий, которые здесь происходят. Использование вышеприведенной идиомы читаемо и фактически неэффективно.

P.S. Я иногда предпочитаю реализовать operator+ как не-член, чтобы использовать неявное преобразование для обеих сторон операторов (только если это имеет смысл).

1

Насколько я помню, Stroustrup's «Язык программирования C++» рекомендует применять операторы в качестве функций-членов только в том случае, когда на внутреннее представление влияет работа и как внешние функции, когда нет. operator + не требует доступа к внутреннему представлению, если выполняется на основе оператора + =, что и делает.

Таким образом, вы бы:

class MyClass 
{ 
public: 
    MyClass& operator+=(const MyClass &other) 
    { 
    // Implementation 
    return *this; 
    } 
}; 

MyClass operator+(const MyClass &op1, const MyClass &op2) 
{ 
    MyClass r = op1; 
    return r += op2; 
}