Практика кодирования:возвращать по значению или по ссылке при матричном умножении?

StackOverflow https://stackoverflow.com/questions/477452

  •  20-08-2019
  •  | 
  •  

Вопрос

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

То, что мне нужно

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

Matrix a(5,2);
a(4,1) = 6 ;
a(3,1) = 9.4 ;           
...                   // And so on ...

Matrix b(2,9);
b(0,2) = 3;
...                   // And so on ...

// After a while
Matrix i = a * b;

То, что у меня было вчера

На данный момент я перегрузил двух операторов operator* и operator= и до вчерашней ночи они определялись таким образом:

Matrix& operator*(Matrix& m);
Matrix& operator=(Matrix& m);

Оператор* создает экземпляр нового матричного объекта (Matrix return = new Matrix(...)) в куче установите значения, а затем просто:

return *result;

То, что у меня есть сегодня

После того, как Обсуждение Я решил реализовать это "по-другому", чтобы пользователя не беспокоили указатели любого типа и сохранить использование неизменным."Другой способ" заключается в передаче возвращаемого значения operator* по значению:

Matrix operator*(Matrix& m);
Matrix& operator=(Matrix& m);

Оператор* создает экземпляр return в стеке установите значения, а затем верните объект.

С таким подходом связана проблема:это не работает.Оператор= ожидает матрицу&, а оператор* возвращает матрицу.Более того, этот подход мне кажется не очень хорошим по другой причине:Я имею дело с матрицами, которые могут быть очень большими, и цели этой библиотеки должны были быть 1) достаточно хорошими для моего проекта 2) быстрыми, поэтому, вероятно, передача по значению не должна быть вариантом.

Какие решения я исследовал

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

Возможно, единственное решение - передавать по значению, возможно, я не могу получить одновременно эффективность и хороший интерфейс.Но опять же, вы эксперт, и я хотел бы знать ваше мнение.

Это было полезно?

Решение

Проблема, с которой вы столкнулись, заключается в том, что выражение a * b создает временный object, а в C ++ временному не разрешается привязываться к непостоянной ссылке, что и является вашим Matrix& operator=(Matrix& m) берет.Если вы измените его на:

Matrix& operator=(Matrix const& m);

Теперь код должен быть скомпилирован.А также очевидное преимущество создания компилируемого кода :), добавляющего const также сообщает вашим абонентам, что вы не будете изменять аргумент m, что может оказаться полезной информацией.

Вы также должны сделать то же самое для своего operator*():

Matrix operator*(Matrix const& m) const;

[ПРАВИТЬ: Дополнительный const в конце указывается, что метод обещает не изменять *this, объект на левая сторона и об умножении тоже.Это необходимо для того, чтобы справиться с такими выражениями, как a * b * c -- подвыражение a * b создает временный и не будет привязываться без const в конце концов.Спасибо Грегу Роджерсу за то, что указал на это в комментариях.]

P.S.Причина, по которой C ++ не разрешает временную привязку к непостоянной ссылке, заключается в том, что временные объекты существуют (как следует из названия) лишь очень короткое время, и в большинстве случаев было бы ошибкой пытаться их модифицировать.

Другие советы

Вы должны действительно прочитать Эффективный C ++ Скотта Мейерса, у него есть отличные темы по этому. Как уже говорилось, лучшие подписи для operator= и operator* являются

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m) const;

но я должен сказать, что вы должны реализовать код умножения в

Matrix& operator*=(Matrix const& m);

и просто используйте его в <=>

Matrix operator*(Matrix const &m) const {
    return Matrix(*this) *= m;
}

Таким образом, пользователь мог бы умножать без создания новых матриц, когда он хочет. Конечно, чтобы этот код работал, у вас также должен быть конструктор копирования:)

Примечание. Начните с предложений Vadims. Следующее обсуждение является спорным, если мы говорим только об очень маленьких матрицах, например, если вы ограничиваете себя матрицами 3х3 или 4х4. Кроме того, я надеюсь, что я не пытаюсь втиснуть в вас много идей:)

Поскольку матрица является потенциально тяжелым объектом, вам определенно следует избегать глубоких копий. Умные указатели являются одной утилитой для ее реализации, но они не решают вашу проблему «из коробки».

В вашем сценарии есть два общих подхода. В обоих случаях любая копия (например, a=b) копирует только ссылку и увеличивает счетчик ссылок (это то, что умные указатели могут сделать для вас).

При Копировать при записи глубокое копирование задерживается до внесения изменений. например вызов функции-члена, такой как void Matrix.TransFormMe() on b, увидит, что на фактические данные ссылаются два объекта (a и b), и создаст глубокую копию перед выполнением преобразования.

Чистый эффект в том, что ваш матричный класс действует как " normal " объект, но количество глубоких копий, фактически сделанных, значительно сокращается.

Другой подход - это неизменяемые объекты , где сам API никогда не модифицирует существующий объект - любая модификация создает новый объект. Таким образом, вместо элемента void TransformMe()' member transforming the contained matrix, Matrix contains only a Matrix GetTransformed () `, возвращающего копию данных.

Какой метод лучше, зависит от фактических данных. В MFC CString является копированием при записи, в .NET a String является неизменным. Неизменяемым классам часто нужен класс построителя (например, StringBuilder), который избегает копий многих последовательных модификаций. Объекты Copy-On-Write требуют тщательного проектирования, чтобы в API было понятно, какой элемент изменяет внутренние элементы, а какой возвращает копию.

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

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

class CowPtr<T>
{
     refcounting_ptr<T> m_actualData;
   public:
     void MakeUnique()
     {
        if (m_actualData.refcount() > 1)
           m_actualData = m_actualData.DeepCopy();
     }
     // ...remaining smart pointer interface...
}

class MatrixData // not visible to user
{
  std::vector<...> myActualMatrixData;
}

class Matrix
{
  CowPtr<MatrixData> m_ptr; // the simple reference that will be copied on assignment

  double operator()(int row, int col)  const
  {  // a non-modifying member. 
     return m_ptr->GetElement(row, col);
  }

  void Transform()
  {
    m_ptr.MakeUnique(); // we are going to modify the data, so make sure 
                        // we don't modify other references to the same MatrixData
    m_ptr->Transform();
  }
}

... Являются всеми, кроме const, поскольку все они вызывают (при необходимости):

void lupp();

Это обновляет кэшированный L, U и P.То же самое означает и get_inverse() этот звонок lupp() а также наборы Matrix* Matrix::inverse.Это вызывает проблему с:

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m);

техника.

Пожалуйста, объясните, как это вызывает проблемы.Обычно так не должно быть.Кроме того, если вы используете переменные-члены для кэширования временных результатов, сделайте их mutable.Тогда вы можете изменять их даже в const Объекты.

Да, ваше предложение хорошее, и я признаю, что я не знал о проблеме с временными объектами с неконстантными ссылками.Но мой класс Matrix также содержит средства для получения факторизации LU (исключение Гаусса).:

const Matrix& get_inverse();
const Matrix& get_l();
const Matrix& get_u();
const Matrix& get_p();

Есть все, кроме const поскольку все они звонят (при необходимости):

void lupp();

Это обновляет кэшированные L, U и P.То же самое означает и get_inverse() который вызывает lupp(), а также устанавливает Matrix* Matrix::inverse.Это вызывает проблему с:

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m);

техника.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top