Общие ловушки
Ошибка компиляции с методами шаблонов
См. эту страницу.
Алиасинг
Не пропустите эту страницу об алиасинге, особенно если вы получили неправильные результаты в выражениях, где целевой объект появляется в правой части.
Проблемы с выравниванием (проверка во время выполнения)
Eigen использует явную векторизацию, и хотя это ценится многими пользователями, это также приводит к некоторым проблемам в особых ситуациях, когда нарушается выравнивание данных. Действительно, до C++17 в C++ не было достаточно хорошей поддержки явного выравнивания данных. В этом случае ваша программа столкнётся с ошибкой проверки (т.е. «контролируемым сбоем») с сообщением, в котором вас попросят обратиться к этой странице:
http://eigen.tuxfamily.org/dox/group__TopicUnalignedArrayAssert.html
Посмотрите на неё и посмотрите сами, можете ли вы с этим справиться. Она содержит подробную информацию о том, как справиться с каждой известной причиной этой проблемы.
А теперь, если вам не важна векторизация и поэтому вы не хотите, чтобы вас раздражали эти проблемы с выравниванием? Тогда прочтите как от них избавиться.
C++11 и ключевое слово auto
Короче: не используйте ключевое слово auto с выражениями Eigen, если вы на 100% уверены в том, что делаете. В частности, не используйте ключевое слово auto в качестве замены типа Matrix<>. Вот пример:
MatrixXd A, B; auto C = A*B; for(...) { ... w = C * v; ...}
В этом примере тип C не является MatrixXd, а абстрактным выражением, представляющим матричное произведение и хранящим ссылки на A и B. Поэтому произведение A*B будет выполняться несколько раз, один раз за итерацию цикла for. Кроме того, если коэффициенты A или B меняются во время итерации, то C будет принимать разные значения, как в следующем примере:
MatrixXd A = ..., B = ...;
auto C = A*B;
MatrixXd R1 = C;
A = ...;
MatrixXd R2 = C;
в результате чего мы получим R1 ≠ R2.
Вот ещё один пример, приводящий к segfault:
auto C = ((A+B).eval()).transpose(); // do something with C
Проблема в том, что eval() возвращает временный объект (в данном случае MatrixXd), на который затем ссылается выражение Transpose<>. Однако этот временный объект удаляется сразу после первой строки, а затем выражение C ссылается на удалённый объект. Одно из возможных решений состоит в применении eval() ко всему выражению:
auto C = (A+B).transpose().eval();
Та же проблема может возникнуть, когда подвыражения автоматически оцениваются Eigen, как в следующем примере:
VectorXd u, v; auto C = u + (A*v).normalized(); // do something with C
Здесь метод normalized() должен оценить дорогостоящее произведение A*v, чтобы избежать его вычисления дважды. Опять же, одним из возможных решений является вызов .eval() на всё выражение:
auto C = (u + (A*v).normalized()).eval();
В этом случае C будет обычным объектом VectorXd. Обратите внимание, что DenseBase::eval() достаточно умён, чтобы избежать копирования, когда базовое выражение уже является обычным Matrix<>.
Проблемы с заголовками (не удаётся скомпилировать)
При работе со всеми библиотеками необходимо проверить документацию, чтобы узнать, какой заголовок включать. То же самое верно и для Eigen, но немного хуже: в Eigen метод класса может потребовать дополнительного #include по сравнению с тем, что требуется самому классу! Например, если вы хотите использовать метод cross() для вектора (он вычисляет векторное произведение), то вам нужно:
#include<Eigen/Geometry>
Мы стараемся всегда документировать это, но сообщите нам, если мы что-то забыли.
Тернарный оператор
Короче: избегайте использования тернарного оператора (COND ? THEN : ELSE) с выражениями Eigen для операторов THEN и ELSE. Чтобы понять почему, рассмотрим следующий пример:
Vector3f A; A << 1, 2, 3; Vector3f B = ((1 < 0) ? (A.reverse()) : A);
Этот пример вернёт B = 3, 2, 1. Видите, почему? Причина в том, что в C++ тип выражения ELSE выводится из типа выражения THEN таким образом, чтобы оба совпадали. Поскольку THEN является Reverse<Vector3f>, оператор ELSE A преобразуется в Reverse<Vector3f>, и компилятор, таким образом, генерирует:
Vector3f B = ((1 < 0) ? (A.reverse()) : Reverse<Vector3f>(A));
В этом очень частном случае обходным путём было бы вызвать A.reverse().eval() для оператора THEN, но самым безопасным и быстрым способом действительно является избегание тернарного оператора с выражениями Eigen и использование конструкции if/else.
Передача по значению
Если вы не знаете, почему передача по значению неверна с Eigen, сначала прочитайте эту страницу.
Хотя вы можете быть очень внимательны и следить за тем, чтобы весь ваш код, который явно использует типы Eigen, передавался по ссылке, вам нужно следить за шаблонами, которые определяют типы аргументов во время компиляции.
Если шаблон имеет функцию, которая принимает аргументы по значению, и соответствующий параметр шаблона оказывается типом Eigen, то, конечно, у вас возникнут те же проблемы с выравниванием, что и в явно определённой функции, передающей типы Eigen по ссылке.
Использование типов Eigen с другими сторонними библиотеками или даже STL может вызвать ту же проблему. boost::bind , например, использует передачу по значению для хранения аргументов в возвращаемом фанкторе. Это, конечно, будет проблемой.
Есть по крайней мере два способа решения этой проблемы:
- Если значение, которое вы передаёте, гарантированно существует на протяжении всего жизненного цикла фанктора, вы можете использовать boost::ref() для упаковки значения при передаче его в boost::bind. Как правило, это не решение для значений в стеке, поскольку если фанктор когда-либо передаётся в более низкий или независимый контекст, объект может исчезнуть к тому времени, когда его попытаются использовать.
- Другой вариант состоит в том, чтобы ваши функции принимали указатель на ссылку со счётом ссылок, например, boost::shared_ptr, в качестве аргумента. Это позволяет избежать необходимости беспокоиться о управлении жизненным циклом передаваемого объекта.
Матрицы с булевыми коэффициентами
Текущее поведение использования Matrix с булевыми коэффициентами не является последовательным и, вероятно, изменится в будущих версиях Eigen, поэтому используйте его с осторожностью!
Простым примером такой несогласованности является
template<int Size> void foo() { Eigen::Matrix<bool, Size, Size> A, B, C; A.setOnes(); B.setOnes(); C = A * B - A * B; std::cout << C << "\n"; }
так как вызов foo<3>() печатает нулевую матрицу, а вызов foo<10>() печатает единичную матрицу.
© Eigen.
Licensed under the MPL2 License.
https://eigen.tuxfamily.org/dox/TopicPitfalls.html