Правильный способ использования Eigen :: Transpose ? - PullRequest
1 голос
/ 19 марта 2020

У меня есть класс вращения, который может использовать матрицы кватернионов или вращения для представления вращений. У меня нет проблем, когда функция транспонирования определена, как показано ниже:

Matrix3 Rot3::transpose() const {
  // quaternion_ is a class variable of type `Quaternion`.
  return quaternion_.toRotationMatrix().transpose();
}

Когда я переключаюсь на рекомендуемую версию, используя Eigen::Transpose, мои модульные тесты не проходят (и я получаю NaNs)

Eigen::Transpose<const Matrix3> Rot3::transpose() const {
  // quaternion_ is a class variable of type `Quaternion`.
  return quaternion_.toRotationMatrix().eval().transpose();
}

Мне нужно использовать .eval() таким странным способом, иначе компилятор выдаст неясное сообщение об ошибке. Я предполагаю, что мое использование декларации Eigen::Transpose не совпадает с тем, что я возвращаю. Любая помощь или предложения относительно того, почему этот метод ведет себя так странно, и какие-либо рекомендации для правильного способа сделать это?

1 Ответ

1 голос
/ 30 марта 2020

Eigen::Transpose - это просто класс обертка вокруг существующей матрицы. Он сохраняет ссылку на матрицу и передает вызов для доступа к элементам, одновременно изменяя индексы. Цель этого класса - иметь возможность использовать транспонированную матрицу без необходимости фактически копировать саму матрицу.

Другими словами, это очень упрощенная версия определения класса Transpose:

struct Transpose<const Matrix3> {

   const Matrix3& m_matrix;

   const float& coeffRef(int row, int col) const { 
       return m_matrix.coeffRef(col,row);
   }
}

Вы можете увидеть фактический источник здесь .

Критическая часть заключается в том, что класс Transpose хранит ссылку на данную матрицу.

Что теперь происходит во второй версии функции transpose?

  • вы создаете временную Matrix3 с помощью quaternion.toRotationMatrix().eval()
  • , затем вы создаете Transpose обертка вокруг этой временной матрицы, хранящая ссылку на эту матрицу.

Когда вы возвращаете из функции, возвращаемая Transpose имеет ссылку на объект, который вышел из области видимости. , Это называется свисающая ссылка , что приводит к неопределенному поведению (причина, по которой вы видите NaN s, наиболее вероятно, что память, в которой находилась ваша временная матрица, была перезаписана другими данными). Код эквивалентен этому:

Eigen::Transpose<const Matrix3> Rot3::transpose() const {
  // Equivalent to your second version
  Matrix3 temp = quaternion_.toRotationMatrix().eval();
  Transpose<const Matrix3> result = temp.transpose(); // Holds ref to temp -> Leads to UB
  return result; 
}

Обратите внимание, что это не происходит в первой версии, потому что ваш тип возврата - Matrix3. В этом случае создаваемый вами объект Transpose преобразуется в новый возвращаемый объект Matrix3, который копирует коэффициенты. Вот эквивалентная версия вашей первой функции.

Matrix3 Rot3::transpose() const {
  // Equivalent to your first version
  Matrix3 temp = quaternion_.toRotationMatrix().eval();
  Matrix3 result = temp.transpose(); // Does a full copy of the transpose of `temp`
  return result; 
}

Если вы по-прежнему хотите использовать Eigen::Transpose в качестве типа возврата (возможно, вы действительно хотите избежать операции копирования) вам нужно хранить матрицу вращения в постоянном месте (например, как кешированную Matrix3 переменную-член в вашем классе), чтобы избежать этой проблемы со ссылками.

Другой вариант - пропустить уже существующую матрицу для заполнения, например,

void Rot3::setToTranspose() (Matrix3& result)
{
     result = quaternion_.toRotationMatrix().transpose();
}
...