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

Я хотел бы использовать библиотеку матриц 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_ общедоступным и просто написать выражения вроде:

UsingEigen a, b, c, d;
a.data_ = b.data_ + c.data_ + d.data_;

Это нарушает инкапсуляцию, но сохраняет эффективность Eigen.

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

Извините, если вопрос носит слишком общий характер. Я новичок и спросить не у кого. До сих пор я везде использовал std::vector<float>, но теперь мне нужно использовать и матрицы. Переход с std::vector<float> на Eigen во всем моем проекте — большой шаг, и я боюсь сделать неверный вызов в самом начале. Любые советы приветствуются!


person Martin Drozdik    schedule 11.06.2012    source источник
comment
Интересная проблема. Однако первое: почему вы хотите инкапсулировать векторы библиотеки Eigen таким образом? Какое поведение добавляют ваши классы?   -  person Konrad Rudolph    schedule 11.06.2012
comment
Мои классы не добавляют никакой функциональности к самим классам библиотеки Eigen, а используют их. Например, один из моих основных классов хранит два вектора. Один как вход для определенного математического вычисления, а другой как выход. Эти объекты должны взаимодействовать таким же образом, как я упоминал выше. Когда вы добавляете два таких объекта, входы должны быть добавлены.   -  person Martin Drozdik    schedule 11.06.2012
comment
Я не думаю, что это возможно без самостоятельного воспроизведения значительной части структуры шаблона выражения. Например, (a+b)*c будет что-то вроде ExprCwiseAdd*UsingEigen (название выдумано, больше не вспоминайте), а ExprCwiseAdd*UsingEigen должно быть где-то определено, но также и ExprCwiseAdd*ExprCWiseAdd и так далее. Короче говоря, у дополнения не будет UsingEigen в качестве возвращаемого типа. (Вы можете взглянуть на boost::proto который является основой для шаблонов выражений). Удачи.   -  person eudoxos    schedule 11.06.2012
comment
Кроме того, в связи с этой темой есть два принципа, которые я усвоил из книг: 1. Редко бывает веская причина публиковать данные. 2. При использовании внешних библиотек остальная часть проекта должна быть ограждена от изменений в них с помощью класса интерфейса.   -  person Martin Drozdik    schedule 11.06.2012
comment
@Martin В этом случае я бы попытался смоделировать все части рабочего процесса, которые делают, выполняют вычисления исключительно с точки зрения классов Eigen и используют только ваши оболочки в качестве соответствующих входных и выходных данных. Затем промежуточные шаги используют фактические Eigen данные. И вам не нужно раскрывать переменные, вы можете использовать функции, которые возвращают (например) ссылки на классы. Правда, это все еще выставляет данные. Я чувствую твою боль.   -  person Konrad Rudolph    schedule 11.06.2012
comment
Если вы когда-нибудь решите использовать шаблоны выражений в своем собственном коде, сделайте себе одолжение и взгляните на Boost. Прото.   -  person Luc Touraille    schedule 11.06.2012


Ответы (4)


Почему раскрытие data_ может нарушить инкапсуляцию? Инкапсуляция означает сокрытие деталей реализации и раскрытие только интерфейса. Если ваш класс-оболочка UsingEigen не добавляет никакого поведения или состояния к собственной Eigen библиотеке, интерфейс не меняется. В этом случае вы должны полностью отказаться от этой оболочки и написать свою программу, используя Eigen структуры данных.

Предоставление матрицы или вектора не нарушает инкапсуляцию: это может сделать только раскрытие реализации матрицы или вектора. Библиотека Eigen предоставляет арифметические операторы, но не их реализацию.

С библиотеками шаблонов выражений пользователи чаще всего расширяют функциональные возможности библиотеки, добавляя поведение, а не добавляя состояние. А для добавления поведения вам не нужно писать классы-оболочки: вы также можете добавить функции, не являющиеся членами, которые реализованы в терминах функций-членов класса Eigen. См. эту колонку "Как функции, не являющиеся членами, улучшают инкапсуляцию" Скотта Мейерса.

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

person TemplateRex    schedule 11.06.2012
comment
Предоставление членов данных нарушает инкапсуляцию. Но добавление перегруженных шаблонов выражений для классов Eigen сломает его таким же образом. - person Konrad Rudolph; 11.06.2012
comment
@KonradRudolph Это не нарушает инкапсуляцию, если UsingEigen имеет точно такой же интерфейс и с точно такой же реализацией (т. Е. Буквальный класс-оболочка с чистой переадресацией и без добавления ведения журнала / проверки и т. Д.). Вы были бы правы, если бы UsingEigen определял дополнительный уровень абстракции, но, похоже, это не так (и его вообще не следует называть UsingEigen, потому что это раскрывает реализацию!) - person TemplateRex; 11.06.2012

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

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

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

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

person PermanentGuest    schedule 11.06.2012

Настройте шаблон класса для хранения общих собственных выражений и сделайте 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;

непосредственно оценить его.

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

person davidhigh    schedule 18.05.2018

Я не понимаю всех ваших вопросов, я постараюсь ответить вам на большинство из них. В этом предложении:

 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>>

Я не уверен, но это легко, с другой стороны, вы можете использовать простую матрицу следующим образом:

float YourMatrix [размер][размер];

Я надеюсь, что это может помочь вам. Я не понимаю всего вашего вопроса, если вам нужно что-то еще, добавьте меня в google+, и я постараюсь вам помочь.

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

person Manuel Cantonero    schedule 11.06.2012
comment
К сожалению, вы ошибаетесь. Вам нужно прочитать, что такое шаблоны выражений и как они влияют на этот код. Ваши перегруженные операторы не будут использовать преимущества шаблонов выражений, как описано в OP. Кроме того, библиотека Eigen предоставляет гораздо больше семантики, чем показанный вами наивный подход с вложенными массивами, который совершенно не подходит для OP. - person Konrad Rudolph; 11.06.2012
comment
Спасибо, Мануэль. Если я воспользуюсь вашим подходом, программа скомпилируется и будет работать правильно. Но такие выражения, как a = b + c + d; приведет к чему-то вроде этого: сначала b + c будут вычислены и сохранены во временном файле bctemp. Затем bctemp будет добавлен к d и создаст bcdtemp. Наконец, bcdtemp будет назначен a. То есть 3 петли и 2 временные. Моя цель - избежать этого. - person Martin Drozdik; 11.06.2012
comment
@Manuel Если вам интересно, что я имею в виду, избегая временных и циклов, вы можете найти лучшее объяснение здесь eigen.tuxfamily.org/dox/TopicInsideEigenExample.html - person Martin Drozdik; 11.06.2012
comment
спасибо! буду читать. Сожалею, что не могу вам помочь. :( Я надеюсь, что кто-то поможет вам как можно скорее. - person Manuel Cantonero; 11.06.2012