Оператор + для матриц в C ++ - PullRequest
5 голосов
/ 17 марта 2010

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

правый

Но если матрицы большие, это неэффективно, поскольку мы делаем одну ненужную копию (возвращаем результат).

Поэтому, если мы не хотим быть эффективными, мы должны забыть чистый вызов:

c = a + b;

правый

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

Ответы [ 8 ]

12 голосов
/ 17 марта 2010

Стандарт C ++ дает разрешение компилятору исключать ненужную копию в этом случае (это называется «именованная оптимизация возвращаемого значения», обычно сокращенно NRVO). Соответствующее «RVO» для случая, когда вы возвращаете временную вместо именованной переменной.

Почти все сравнительно недавние компиляторы C ++ реализуют как NRVO, так и RVO, поэтому, вообще говоря, вы можете игнорировать тот факт, что в противном случае эта конструкция не была бы особенно эффективной.

Редактировать: Я, конечно, говорил о копии, связанной с возвратом новой матрицы, содержащей результат сложения. Вы, вероятно, do хотите либо передать ввод по константной ссылке:

Matrix operator+(Matrix const &other) const { 
    Matrix result;
    // ...
    return result;
}

... или же, передать по значению, но вернуть переданное значение:

Matrix operator+(Matrix other) const { 
    other += *this;
    return other;
}

Обратите внимание, что это зависит от коммутативности (то есть, на самом деле он делает b+a вместо a+b), поэтому, хотя он подходит для сложения, он не будет работать для некоторых других операций.

3 голосов
/ 17 марта 2010

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

2 голосов
/ 17 марта 2010

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

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

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

void add(Matrix & result, const Matrix & lhs, const Matrix & rhs) ;

Если вы хотите сделать это операторским способом (что является моим предпочтительным решением), то вы должны предположить, что 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:

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

1 голос
/ 17 марта 2010

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

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

1 голос
/ 17 марта 2010

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

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

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

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

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

0 голосов
/ 17 марта 2010
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 голосов
/ 17 марта 2010

Я бы попытался построить Матрицу в операторе возврата:

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

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

0 голосов
/ 17 марта 2010

Два способа ее решить.

1) Использовать ссылки:

Matrix& operator+ (Matrix& other) const {

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

...