Я предполагаю наивную реализацию +, оператор для матриц (2D, например) в C++ был бы:
class Matrix {
Matrix operator+ (const Matrix & other) const {
Matrix result;
// fill result with *this.data plus other.data
return result;
}
}
таким образом, мы могли использовать его как
Matrix a;
Matrix b;
Matrix c;
c = a + b;
Правильно?
Но если матрицы являются большими, это не эффективно, поскольку мы делаем один не - необходимая копия (возвратите результат).
Поэтому, Если мы wan't, чтобы быть эффективными мы должны забыть чистый вызов:
c = a + b;
Правильно?
Что Вы предложили бы / предпочитают?Спасибо.
Стандарт C ++ дает компилятору разрешение на удаление ненужной копии в этом случае (это называется «оптимизация именованного возвращаемого значения», обычно сокращенно NRVO) . Есть соответствующий "RVO", когда вы возвращаете временную вместо именованной переменной.
Почти все сравнительно недавние компиляторы C ++ реализуют как NRVO, так и RVO, так что, вообще говоря, вы можете игнорировать тот факт, что в противном случае эта конструкция не была бы особенно эффективной.
Редактировать: Я, конечно, говорил о копии, участвующей в возврате новой матрицы, содержащей результат сложения. Вы, вероятно, действительно хотите передать ввод по константной ссылке:
Matrix operator+(Matrix const &other) const {
Matrix result;
// ...
return result;
}
... или иначе, передать по значению, но вернуть переданное значение:
Matrix operator+(Matrix other) const {
other += *this;
return other;
}
Обратите внимание, что это зависит от коммутативности (т. Е. , он действительно выполняет b + a
вместо a + b
), поэтому, хотя он подходит для сложения, он не будет работать для некоторых других операций.
class Matrix {
Matrix & operator+=(const Matrix & other) {
// fill result with *this.data plus other.data
// elements += other elements
return *this;
}
Matrix operator+ (const Matrix & other) {
Matrix result = *this;
return result += other;
}
}
Как отмечали другие, ваша реализация не так дорога, как вы думаете. Однако может иметь смысл определить дополнительные методы, которые изменяют объект на месте для использования в критических внутренних циклах.
РЕДАКТИРОВАТЬ - исправлено после абзаца
Дело в том, что даже с оптимизацией возвращаемого значения вы все равно конструируете локальную переменную, а затем присваиваете ее переменной результата после выхода operator +. И, конечно, уничтожение этого лишнего объекта. Еще есть дополнительный объект, используемый для временного хранения результата. Можно выполнять подсчет ссылок с копированием при записи, но это добавляет накладные расходы на разыменование при каждом использовании матрицы.
В 99% случаев это не проблема, но время от времени возникают критические ситуации.Если бы вы имели дело с большими матрицами, накладные расходы на подсчет ссылок были бы незначительными - но для 2D до 4D бывают моменты, когда вас могут очень заботить эти несколько дополнительных циклов - или, что более важно, о , а не помещает матрицу в кучу, когда вы хотите, чтобы она была в стеке или встроена в какую-либо структуру / класс / массив.
Тем не менее - в таких случаях вы, вероятно, не будете писать свой собственный код матрицы - вы просто будете использовать операции матрицы из DirectX, OpenGL или чего-то еще.
Вы можете вернуть значение, не вызывая создание копии. Его так называемые ссылки на R-значение Подробно объяснены здесь http://www.artima.com/cppsource/rvalue.html
Если вы действительно обеспокоены производительностью (вы проводили профилирование?), вам, вероятно, не стоит реализовывать operator+ вообще, поскольку вы не можете контролировать, приведет ли это к созданию неоптимального временного объекта. Просто реализуйте operator+= и/или функцию-член add(Matrix& result, const Matrix& in1, const Matrix& in2)
и позвольте вашим клиентам создавать правильные временные объекты.
Если вам нужен оператор+, то любой из вариантов Джерри Коффина будет работать отлично.
Два способа решить эту проблему.
1) Используйте ссылки:
Matrix& operator+ (Matrix& other) const {
2) Используйте неглубокую копию в конструкторе копирования. Да, новый объект Matrix будет создан, но реальная матрица больше не будет создана
Обратите внимание, что ваша первая наивная реализация очень родная, поскольку ничего не передается по ссылке. Я предполагаю, что это был действительно наивный пример и что нет необходимости напоминать читателям о преимуществах передачи по ссылке вместо передачи по значению.
Также обратите внимание, что я использовал операторы, не являющиеся функциями-членами, вместо функций-членов, но в конечном итоге результаты (почти) такие же.
Если вы хотите быть уверенным, что не будет создана необходимая копия, вам следует попробовать неоператорную версию:
void add(Matrix & result, const Matrix & lhs, const Matrix & rhs) ;
Если вы хотите сделать это операторным способом (что является моим предпочтительным решением), тогда вы должны предположить, что оператор + создам временный. Затем вы должны определить как operator +, так и operator + =:
Matrix & operator += (Matrix & result, const Matrix & rhs) ;
{
// add rhs to result, and return result
return result ;
}
Matrix operator + (const Matrix & lhs, const Matrix & rhs) ;
{
Matrix result(lhs) ;
result += rhs ;
return result ;
}
Теперь вы можете попробовать «усилить» оптимизацию компилятора и записать это как:
Matrix & operator += (Matrix & result, const Matrix & rhs) ;
{
// add rhs to result, and return result
return result ;
}
Matrix operator + (Matrix lhs, const Matrix & rhs)
{
return lhs += rhs ;
}
Как было предложено Хербом Саттером в Стандартах кодирования C ++ , 27. Предпочитайте канонические формы арифметических операторов и операторов присваивания , p48-49:
Вариант состоит в том, чтобы оператор @ [@ будучи +, -, что угодно] принимал свой первый параметр по значению. Таким образом, вы настраиваете сам компилятор для выполнения копирования за вас неявно, и это может дать компилятору больше свободы в применении оптимизаций.
Я бы попытался построить матрицу по оператору return:
Matrix Matrix::operator + (const Matrix& M)
{
return Matrix(
// 16 parameters defining the 4x4 matrix here
// e.g. m00 + M.m00, m01 + M.m01, ...
);
}
Таким образом вы не будете использовать какие-либо временные библиотеки.