Как интегрировать библиотеку, которая использует шаблоны выражений?
Я хотел бы использовать матрицу Eigen в качестве движка линейной алгебры в моей программе. Eigen использует шаблоны выражений для реализации отложенных вычислений и упрощения циклов и вычислений.
Например:
#include<Eigen/Core>
int main()
{
int size = 40;
// VectorXf is a vector of floats, with dynamic size.
Eigen::VectorXf u(size), v(size), w(size), z(size);
u = 2*v + w + 0.2*z;
}
Так как Eigen использует шаблоны выражений,
u = 2*v + w + 0.2*z;
В вышеупомянутом образце сокращается до одной петли длиной 10 (а не 40, поплавки вводятся в регулятор кусками по 4) без создания временного. Как это круто?
Но если я интегрирую библиотеку так:
class UsingEigen
{
public:
UsingEigen(const Eigen::VectorXf& data):
data_(data)
{}
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
...
private:
Eigen::VectorXf data_;
}
Тогда выражения как:
UsingEigen a, b, c, d;
a = b + c + d;
не может воспользоваться тем, как реализован Eigen. И это еще не все. Есть много других примеров, где шаблоны выражений используются в Eigen.
Простым решением было бы не определять операторов самостоятельно, сделать data_
public и просто пишите выражения вроде:
UsingEigen a, b, c, d;
a.data_ = b.data_ + c.data_ + d.data_;
Это нарушает инкапсуляцию, но сохраняет эффективность Eigen.
Другим способом может быть создание собственных операторов, но пусть они возвращают шаблоны выражений. Но так как я новичок в C++, я не знаю, правильный ли это путь.
Извините, если вопрос носит слишком общий характер. Я новичок и мне не с кем спросить. До сих пор я использовал std::vector<float>
везде, но теперь мне нужно использовать матрицы также. Переключиться с std::vector<float>
для Eigen во всем моем проекте это большой шаг, и я боюсь сделать неправильный звонок прямо в начале. Любой совет приветствуется!
4 ответа
Зачем выставлять data_
нарушить инкапсуляцию? Инкапсуляция означает скрытие деталей реализации и только разоблачение интерфейса. Если ваш класс обертки UsingEigen
не добавляет никакого поведения или состояния к нативному Eigen
библиотека, интерфейс не меняется. В этом случае вам следует полностью удалить эту оболочку и написать свою программу, используя Eigen
структуры данных.
Экспонирование матрицы или вектора не нарушает инкапсуляцию: это может сделать только демонстрация реализации матрицы или вектора. Eigen
библиотека раскрывает арифметические операторы, но не их реализацию.
В библиотеках шаблонов выражений наиболее распространенным способом расширения функциональности библиотеки является добавление поведения, а не добавление путем добавления состояния. А для добавления поведения вам не нужно писать классы-обертки: вы также можете добавить функции, не являющиеся членами, которые реализованы в терминах Eigen
функции члена класса. Смотрите этот столбец "Как функции, не являющиеся членами, улучшают инкапсуляцию" Скотта Мейерса.
Что касается вашего беспокойства, что преобразование вашей текущей программы в версию, которая явно использует Eigen
функциональность: вы можете выполнять пошаговые изменения, каждый раз меняя небольшие части вашей программы, следя за тем, чтобы ваши юнит-тесты (у вас есть юнит-тесты, не так ли?) не прерывались по мере продвижения.
На мой взгляд, это скорее проблема объектно-ориентированного проектирования, чем проблема использования библиотеки. Все, что вы читаете из книг, - это правильные рекомендации. т.е. не выставляйте переменные-члены и не защищайте верхние уровни от нюансов использования стороннего уровня.
То, что вы могли бы ожидать, это правильные абстракции математических функций, которые могут быть реализованы с помощью этой библиотеки внутри. то есть вы могли бы выставить свою собственную библиотеку с функциями более высокого уровня, чем элементарные векторные и матричные операции. Таким образом, вы можете использовать особенности взаимодействия между объектами библиотеки и в то же время вам не нужно выставлять свои переменные-члены верхним уровням.
Например, вы можете абстрагировать мои API более высокого уровня, такие как вычисление расстояния от точки до плоскости, расстояние между двумя плоскостями, вычисление новых координат точки в другой системе координат с использованием матриц преобразования и т. Д. Для внутренней реализации этих методов вы можете использовать объекты библиотеки. Вы можете ограничить использование классов библиотек в сигнатурах API, чтобы избежать зависимости для верхних уровней этой библиотеки.
Верхние уровни вашей программы должны быть выше по уровню абстракции и не должны беспокоиться об элементарных деталях реализации, таких как способ вычисления расстояния от точки до плоскости и т. Д. Кроме того, им даже не нужно знать, если это ниже слой реализован с использованием этой библиотеки или чего-то еще. Они просто использовали бы интерфейсы вашей библиотеки.
Установите шаблон класса для хранения общих выражений Eigen и сделайте UsingEigen
особый случай этого:
template<typename expr_t>
class UsingEigenExpr
{
UsingEigen(expr_t const& expr) : expr(expr) {}
expr_t expr;
operator UsingEigenExpr<Eigen::VectorXf>() const
{
return {expr};
}
};
using UsingEigen = UsingEigenExpr<Eigen::VectorXf>;
Затем перегрузите любую требуемую функцию, например, как
template<typename expr1_t, typename expr2_t, typename function_t>
auto binary_op(UsingEigenExpr<expr1_t> const& x, UsingEigenExpr<expr2_t> const& y, function_t function)
{
return UsingEigenExpr<decltype(function(std::declval<expr1_t>(),std::declval<expr2_t>()))>(function(x.expr,y.expr));
}
template<typename expr1_t, typename expr2_t>
auto operator+(UsingEigenExpr<expr1_t> const& x, UsingEigenExpr<expr2_t> const& y)
{
return binary_op(x,y,[](auto const& x, auto const& y) {return x+y;});
}
и так далее для других бинарных операторов, таких как operator-
, для унарных операторов, и в целом для всех других вещей, которые вы хотите использовать. Кроме того, вы можете добавить некоторые другие функции-члены в UsingEigenExpr
например, size()
, norm()
, так далее.
Используйте это как
UsingEigen b, c, d;
auto a = b + c + d;
сохранить выражение или
UsingEigen b, c, d;
UsingEigen a = b + c + d;
непосредственно оценить это.
Хотя этот подход работает, в конце вы обнаружите, что дублируете все необходимые функции, поэтому используйте его осторожно.
Я не понимаю всех ваших вопросов, я постараюсь ответить на большинство из них. В этом предложении:
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
У вас есть оператор перегрузки (извините, я не знаю, правильно ли это писать по-английски), поэтому вы можете написать:
a = b + c + d;
вместо:
a.data_ = b.data_ + c.data_ + d.data_;
У вас не будет никаких проблем, стоимость вашей программы будет такой же. Кроме того, у вас будет инкапсуляция и эффективность.
С другой стороны, если вы хотите определить свой собственный оператор, вы можете сделать это так, как это делает шаблон. Вы можете найти информацию в веб-поиске "оператор перегрузки", но похож на это:
UsingEigen operator + (const UsingEigen& adee)const
{
return UsingEigen(data_ + adee.data_);
}
Вместо "+" вы можете поставить оператора и выполнить необходимые операции.
Если вы хотите создать матрицу, это просто. Вам нужно только создать массив из массива или вектора вектора.
Я думаю, что-то вроде этого:
std::vector<vector<float>>
Я не уверен, но это легко, с другой стороны, вы можете использовать простую матрицу следующим образом:
плавать YourMatrix [размер][размер];
Я надеюсь, что это может помочь вам. Я не понимаю все ваши вопросы, если вам нужно что-то еще, добавьте меня в Google +, и я постараюсь вам помочь.
Извините за мой английский, я надеюсь, вы понимаете все, и это помогает вам.