【问题标题】:How to write a third-party library wrapper class around expression templates如何围绕表达式模板编写第三方库包装类
【发布时间】:2015-03-10 16:09:53
【问题描述】:

我们正尝试在我的研究小组中实现一个新的 C++ 代码来执行大型数值模拟(有限元、有限差分法、拓扑优化等)。该软件将被学术界和工业界的人们使用。

对于软件的密集线性代数部分,我们想使用 Eigen 或 Armadillo。我们希望围绕这些包构建一个包装器,原因有两个: 1. 向用户公开我们自己的 API 而不是第三方 API; 2.以防我们将来需要切换库。我知道原因 2 是一种非常昂贵的保险形式,但是我们在使用之前的模拟软件时遇到了这种情况。

我遇到的关于包装第三方库的信息来自以下来源:

我的问题与构建这个包装类的最佳方法有关。理想情况下,薄层包装器是最好的,因为:

template< typename T >
class my_vec {
private:
    arma::Col< T > _arma_vec;
};

或其等效的特征向量。

然后,我的类将调用第三方库类为:

my_vec::foo() { return _arma_vec.foo(); }

我认为(并且我想对此进行确认)这个薄层的问题是我失去了从这些库在引擎盖下实现的表达式模板获得的速度。例如,在犰狳中,以下操作:

// Assuming these vectors were already populated.
a =  b + c + d;

变成这样:

for ( std::size_t i = 0; i < a.size(); ++i ) {
    a[i] = b[i] + c[i] + d[i];
}

由于它们实现了expression templates 而没有创建任何临时对象。同样的情况也适用于 Eigen。

据我所知,我失去表达式模板功能的原因是,虽然 Armadillo 或 Eigen 不会创建自己的临时对象,但我的类 my_vec 会。避免这种情况的唯一方法是在其表达式模板周围构建一个薄层包装器。但是,在这一点上,这似乎违反了 YAGNI 原则。

这里有这个相关的问题:

建议使用类似的东西:

my_vec a, b, c;
// ... populate vectors
a._arma_vec = b._arma_vec + c._arma_vec;

是否可以使用类似的东西来代替?

template< typename T >
arma::Col< T > &
my_vec< T >::data() { return _arma_vec; }

a.data() = b.data() + c.data();

或者使用一些运算符重载来对用户隐藏 data() ? 如果我们不想直接使用这些库,还有哪些其他选择?使用宏?如果我们决定使用 C++11,使用别名?

或者构建这个包装类最方便的方法是什么?

【问题讨论】:

    标签: c++ eigen armadillo expression-templates


    【解决方案1】:

    仅供参考,这就是我决定实现我的解决方案的方式:我以以下方式重载了 operator+:

    template< typename T1, typename T2 >
    auto
    operator+(
            const my_vec< T1 > & X,
            const my_vec< T2 > & Y ) ->decltype( X.data() + Y.data() )
    {
        return X.data() + Y.data();
    }
    
    template< typename T1, typename T2 >
    auto
    operator+(
            const my_vec< T1 > & X,
            const T2 &           Y ) ->decltype( X.data() + Y )
    {
        return X.data() + Y;
    }
    
    template< typename T1, typename T2 >
    auto
    operator+(
            const T1 &           X,
            const my_vec< T2 > & Y ) ->decltype( X + Y.data() )
    {
        return X + Y.data();
    }
    

    然后,我在 my_vec 类中重载了我的 operator=,如下所示:

    template< typename T >
    template< typename A >
    const my_vec< T > &
    my_vec< T >::operator=(
            const A & X )
    {
        _arma_vec = X;
    
        return *this;
    }
    

    【讨论】:

    • 刚刚偶然发现了这个问题 - 我也在努力实现类似的目标。查看您的代码时会想到几个问题: 1. 您的包装类是否只有一个向量私有成员?对于矩阵成员,您是否编写了另一个包装类? 2、你是如何完成矩阵向量乘法的? 3. 你的实现是否保留了表达式模板的效率?
    • 1.是的。或 Row,或 Matrix 私有成员。您可以为每个类(Col、Row、Mat)编写一个包装器,或者只为所有内容使用一个矩阵并编写一个包装器。 2.您可以编写多个包装器,或单个包装器,正如我在 1 中所说的那样。然后您需要重载运算符。 3. 它很高效,但从来没有原始实现那么高效。我想说我们在计算时间上损失了 1% 到 10% 的效率。我们获得的是在库之间切换的能力。现在,我们可以轻松地在 Eigen 和 Armadillo 之间切换,而无需更改公共 API。
    • 我一直在尝试为矩阵和向量编写一个矩阵包装器类,这样我就可以将重载的运算符放在一个包装器文件中。然而,Eigen 并不容易做到这一点。我只想有一个私有成员 - 一个未初始化的 Matrix - 可以调整为 MxN 矩阵或 Mx1 向量的大小,但特征向量操作(如 dot prod)不起作用,因为需要将向量初始化为 Matrix.
    • 您仍然可以拥有一个 Matrix 包装器。但是,对于某些函数,您需要在调用 Eigen API 之前将 Eigen 矩阵转换为 Eigen 向量。这永远不会暴露给用户,它是在函数内部完成的。
    猜你喜欢
    • 2011-06-11
    • 1970-01-01
    • 2022-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多