Как интегрировать библиотеку, которая использует шаблоны выражений?

Я хотел бы использовать матрицу 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 +, и я постараюсь вам помочь.

Извините за мой английский, я надеюсь, вы понимаете все, и это помогает вам.

Другие вопросы по тегам