Кто удаляет скопированный экземпляр в операторе +? (C ++) - PullRequest
9 голосов
/ 19 мая 2010

Я искал, как правильно реализовать + оператор по всему интернету, и все найденные результаты делают следующие шаги:

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. Разве не глупо так реализовывать оператор +, он вызывает оператор присваивания (который копирует класс) в первой строке, а затем конструктор копирования в возврате (который также копирует класс из-за тот факт, что возврат по значению, поэтому он уничтожает первую копию и создает новую .. которая, честно говоря, не очень умная ...)

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

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

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

Ответы [ 8 ]

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

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

  3. Не совсем; этот метод гораздо эффективнее, чем кажется на первый взгляд.

5 голосов
/ 19 мая 2010

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

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

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

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

3 голосов
/ 19 мая 2010

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

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

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

Компиляторы могут (и обычно делают) удалить эту копию, используя NRVO.

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

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

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

Не совсем. Это не так неэффективно.

3 голосов
/ 19 мая 2010

Похоже, это правильный способ реализации operator+. Несколько баллов:

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

Я постараюсь ответить:

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

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

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

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

1 голос
/ 19 мая 2010

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

Итак, вы бы получили:

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;
}
1 голос
/ 19 мая 2010
  1. Это правильный способ реализации оператора + в C ++. Большинство копий, которых вы так боитесь, будут исключены компилятором и будут подвергаться семантике перемещения в C ++ 0x.

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

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

1 голос
/ 19 мая 2010

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

...