如何集成使用表达式模板的库?

时间:2012-06-11 07:58:17

标签: c++ templates encapsulation eigen expression-templates

我想在我的程序中使用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是一个很大的进步,我害怕在一开始就拨打错误的电话。欢迎任何建议!

4 个答案:

答案 0 :(得分:5)

为什么暴露data_会破坏封装?封装意味着隐藏实现细节并仅暴露接口。如果您的包装类UsingEigen未向本机Eigen库添加任何行为或状态,则接口不会更改。在这种情况下,您应该完全删除此包装并使用Eigen数据结构编写程序。

暴露矩阵或向量不会打破封装:只暴露矩阵或向量的实现就可以了。 Eigen库公开算术运算符,但不公开它们的实现。

使用表达式模板库,用户扩展库功能的最常用方法是添加行为,而不是通过添加状态添加。对于添加行为,您不需要编写包装类:您还可以添加根据Eigen类成员函数实现的非成员函数。请参阅Scott Meyers撰写的this column“非成员函数如何改进封装”。

至于您担心将当前程序转换为明确使用Eigen功能的版本:您可以逐步执行更改,每次更改程序的一小部分,确保你的单元测试(你有单元测试,不是吗?)随着你的进展不要破坏。

答案 1 :(得分:2)

在我看来,这看起来更像是面向对象的设计问题而不是库使用问题。 无论你从书中读到什么都是正确的建议。即,不要暴露成员变量并保护上层免受第三方层使用的细微差别。

您可以期待的是可以在内部使用此库实现的数学函数的正确抽象。也就是说,您可以使用高级函数公开自己的库,而不是基本向量和矩阵运算。通过这种方式,您可以利用库对象之间交互的特性,同时您不必将成员变量暴露给上层。

例如,您可以抽象出更高级别的API,例如计算从点到平面的距离,两个平面之间的距离,使用变换矩阵计算另一个坐标系的点的新坐标等。实现这些方法在内部,您可以使用库对象。您可以限制不使用API​​签名中使用的任何库类,以避免依赖于此库中的上层。

程序的上层应该在抽象层次上更高,并且不必担心基本的实现细节,例如如何实现从点到平面的距离的计算等。此外,它们甚至不需要知道如果使用此库或其他方式实现此较低层。他们只会使用你的库的接口。

答案 2 :(得分:2)

设置一个类模板来保存一般的特征表达式,并使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;

直接评估它。

虽然这种方法有效,但最终您发现自己会复制所有必需的功能,因此请谨慎使用。

答案 3 :(得分:-2)

我不明白你的所有问题,我会尽力回答你。 在这句话中:

 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 [size] [size];

我希望它可以帮到你。我不明白你的所有问题,如果你需要更多的东西加我谷歌+我会尽力帮助你。

对不起我的英语,我希望你能理解所有这些并帮助你。