17

我想在我的程序中使用 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

4 回答 4

5

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

公开矩阵或向量不会破坏封装:只有公开矩阵或向量的实现才能做到这一点。该Eigen库公开了算术运算符,但不公开它们的实现。

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

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

于 2012-06-11T08:21:04.793 回答
2

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

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

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

您的程序的上层应该在抽象级别上更高,并且不需要关心基本的实现细节,例如如何实现从点到平面的距离的计算等。此外,他们甚至不需要知道这是否更低层是使用这个库或其他东西实现的。他们只会使用您图书馆的接口。

于 2012-06-11T08:30:14.847 回答
2

设置一个类模板来保存一般的 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;

直接评价它。

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

于 2018-05-18T14:35:11.080 回答
-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 [大小] [大小];

我希望它可以帮助你。如果您需要更多内容,请在 google+ 上添加我,我不明白您的所有问题,我会尽力帮助您。

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

于 2012-06-11T08:23:10.743 回答