Оператор + для матриц в C++

Я предполагаю наивную реализацию +, оператор для матриц (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;

Правильно?

Что Вы предложили бы / предпочитают?Спасибо.

5
задан cibercitizen1 17 March 2010 в 17:39
поделиться

8 ответов

Стандарт 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 ), поэтому, хотя он подходит для сложения, он не будет работать для некоторых других операций.

12
ответ дан 18 December 2019 в 08:27
поделиться
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; 
  } 
} 
0
ответ дан 18 December 2019 в 08:27
поделиться

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

РЕДАКТИРОВАТЬ - исправлено после абзаца

Дело в том, что даже с оптимизацией возвращаемого значения вы все равно конструируете локальную переменную, а затем присваиваете ее переменной результата после выхода operator +. И, конечно, уничтожение этого лишнего объекта. Еще есть дополнительный объект, используемый для временного хранения результата. Можно выполнять подсчет ссылок с копированием при записи, но это добавляет накладные расходы на разыменование при каждом использовании матрицы.

В 99% случаев это не проблема, но время от времени возникают критические ситуации.Если бы вы имели дело с большими матрицами, накладные расходы на подсчет ссылок были бы незначительными - но для 2D до 4D бывают моменты, когда вас могут очень заботить эти несколько дополнительных циклов - или, что более важно, о , а не помещает матрицу в кучу, когда вы хотите, чтобы она была в стеке или встроена в какую-либо структуру / класс / массив.

Тем не менее - в таких случаях вы, вероятно, не будете писать свой собственный код матрицы - вы просто будете использовать операции матрицы из DirectX, OpenGL или чего-то еще.

1
ответ дан 18 December 2019 в 08:27
поделиться

Вы можете вернуть значение, не вызывая создание копии. Его так называемые ссылки на R-значение Подробно объяснены здесь http://www.artima.com/cppsource/rvalue.html

3
ответ дан 18 December 2019 в 08:27
поделиться

Если вы действительно обеспокоены производительностью (вы проводили профилирование?), вам, вероятно, не стоит реализовывать operator+ вообще, поскольку вы не можете контролировать, приведет ли это к созданию неоптимального временного объекта. Просто реализуйте operator+= и/или функцию-член add(Matrix& result, const Matrix& in1, const Matrix& in2) и позвольте вашим клиентам создавать правильные временные объекты.

Если вам нужен оператор+, то любой из вариантов Джерри Коффина будет работать отлично.

1
ответ дан 18 December 2019 в 08:27
поделиться

Два способа решить эту проблему.

1) Используйте ссылки:

Matrix& operator+ (Matrix& other) const {

2) Используйте неглубокую копию в конструкторе копирования. Да, новый объект Matrix будет создан, но реальная матрица больше не будет создана

0
ответ дан 18 December 2019 в 08:27
поделиться

Обратите внимание, что ваша первая наивная реализация очень родная, поскольку ничего не передается по ссылке. Я предполагаю, что это был действительно наивный пример и что нет необходимости напоминать читателям о преимуществах передачи по ссылке вместо передачи по значению.

Также обратите внимание, что я использовал операторы, не являющиеся функциями-членами, вместо функций-членов, но в конечном итоге результаты (почти) такие же.

Если вы хотите быть уверенным, что не будет создана необходимая копия, вам следует попробовать неоператорную версию:

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:

Вариант состоит в том, чтобы оператор @ [@ будучи +, -, что угодно] принимал свой первый параметр по значению. Таким образом, вы настраиваете сам компилятор для выполнения копирования за вас неявно, и это может дать компилятору больше свободы в применении оптимизаций.

2
ответ дан 18 December 2019 в 08:27
поделиться

Я бы попытался построить матрицу по оператору return:

Matrix Matrix::operator + (const Matrix& M)
{
    return Matrix(
        // 16 parameters defining the 4x4 matrix here
        // e.g. m00 + M.m00, m01 + M.m01, ...
    );
}

Таким образом вы не будете использовать какие-либо временные библиотеки.

0
ответ дан 18 December 2019 в 08:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: