Я искал, как реализовать + оператор правильно на всем протяжении Интернета и всех результатов, которые я нашел, делают следующие шаги:
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!
}
У меня есть немного вопросов об этом "процессе":
Не настолько глупо реализовать + оператор этот путь, он называет оператор присваивания (который копирует класс) в первой строке, и затем конструктор копии в возврате (который также копирует класс, вследствие того, что возврат значением, таким образом, он уничтожает первую копию и создает новую.. который откровенно не действительно умен...),
Когда я пишу a=b+c, b+c часть создает новую копию класса, затем '=' часть копирует копию в себя. кто удаляет копию это созданный b+c?
Существует ли лучший способ реализовать + оператор, не справляясь класс дважды, и также ни без каких проблем памяти?
заранее спасибо
При таких обстоятельствах я бы, вероятно, подумал о чем-то вроде:
MyClass MyClass::operator+(MyClass other) {
other += *this;
return other;
}
Дэйв Абрахамс недавно написал статью , объясняющую, как это работает и почему такой код обычно достаточно эффективен, хотя изначально похоже, что этого не должно быть.
Изменить (спасибо, MSalters): Да, это предполагает / зависит от коммутативного свойства, сохраняемого для MyClass
. Если a + b! = B + a
, то исходный код - это то, что вам нужно (в основном те же рассуждения).
Похоже, это правильный способ реализации оператора +
. Несколько моментов:
MyClass result = * this
не использует оператор присваивания, он должен вызывать конструктор копирования, как если бы он был написан MyClass result (* this)
. a = b + c
называется временным , и компилятор отвечает за его удаление (что, вероятно, произойдет в конце инструкции то есть точка с запятой после того, как все остальное было сделано). Вам не нужно об этом беспокоиться, компилятор всегда очищает временные файлы. он вызывает оператор присваивания (который копирует класс) в первой строке
Нет, это инициализация копирования (через конструктор).
затем конструктор копирования в возврате (который также копирует класс
. Компиляторы могут (и обычно делают) исключить эту копию с помощью NRVO.
Когда я пишу a = b + c, часть b + c создает новая копия класса, затем часть 'a =' копирует копию самому себе. кто удаляет копию, созданную b + c
Компилятор, как и любое другое временное значение. Они удаляются в конце полного- выражение (в данном случае это означает в или после ;
в конце строки.
Есть ли лучший способ реализовать оператор +, не копируя класс дважды, а также без каких-либо проблем с памятью?
Не совсем. Это не так уж и неэффективно.
Я постараюсь ответить:
Пункт (1): Нет, он не вызывает оператора присваивания. Вместо этого он вызывает конструктор. Поскольку вам все равно нужно создать объект (поскольку operator +
возвращает копию), это не вводит дополнительных операций.
Точка (2): временный результат
создается в стеке и, следовательно, не вызывает проблем с памятью (он уничтожается при выходе из функции). На return
создается временный объект, чтобы можно было использовать назначение (или конструктор копирования) для присвоения результатов a
(в a = b + c;
]) даже после уничтожения результата
. Этот временный объект автоматически уничтожается компилятором.
Пункт (3): Вышеупомянутое - это то, что предписывает стандарт. Помните, что разработчикам компилятора разрешено оптимизировать реализацию до тех пор, пока эффект будет таким же, как предписанный стандартом. Я считаю, что компиляторы на самом деле оптимизируют многие из происходящих здесь копий. Приведенная выше идиома удобочитаема и на самом деле не является неэффективной.
P.S. Иногда я предпочитаю реализовать operator +
как не член, чтобы использовать неявное преобразование для обеих сторон операторов (только если это имеет смысл).
Нет проблем с памятью (при условии, что оператор присваивания и конструктор копирования хорошо написаны). Просто потому, что вся память для этих объектов берется в стек и управляется компилятором. Более того, компиляторы оптимизируют это и выполняют все операции непосредственно с конечным a
вместо того, чтобы копировать дважды.
По сути, это не оператор присваивания, а конструктор копирования. В конце концов, такая операция, как сложение, создает новое значение, поэтому его нужно где-то создать. Это более эффективно, чем кажется, поскольку компилятор может выполнять оптимизацию возвращаемого значения, что означает, что он может создать значение непосредственно там, где оно будет использоваться в следующий раз.
Результат
объявляется как локальная переменная и, следовательно, исчезает при вызове функции - кроме случаев, когда используется RVO (см. Выше), и в этом случае он никогда не создавался в функции, но в звонилке.
Не совсем; этот метод намного эффективнее, чем кажется на первый взгляд.
Это правильный способ реализации оператора+ в C++. Большинство копий, которых вы так боитесь, будут исключены компилятором и будут подчиняться семантике перемещения в C++0x.
Класс является временным и будет удален. Если вы свяжете временный класс с const&
, время жизни временного класса будет увеличено до времени жизни ссылки const.
Реализация в виде свободной функции немного более очевидна. Первый параметр в MyClass::operator+ является неявным this, и компилятор в любом случае перепишет функцию в operator+(const MyClass&, const MyClass&).
Насколько я помню, «Язык программирования 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;
}