【问题标题】:Template Specifications for constructor of n-dimensional arrayn维数组构造函数的模板规范
【发布时间】:2009-07-14 02:51:21
【问题描述】:

我正在实现一个 n 维数组类,它是一个模板,如下所示(注意数据存储在一个线性数组中,其长度是所有维度的乘积):

template< class valType, int rank >
class NDimensionalArray
{
public:

private:
    valType* m_data;
    int* m_dimensions;
    int m_rank;
};

所以这个想法是用户(我)可以指定一个等级为 2 且具有特定维度的数组,即:

NDimensionalArray<double,2> matrix(10,10);

现在的困难在于为 1->n 维专门构造构造函数,每个构造函数接受 n 个参数,其中 n 是数组的秩。现在我想到了在 printf() 中使用类似 valarray 的方法,但是这样定义了一个 2 维的一维数组,即:

NDimensionalArray<double,1> matrix(10,10);

将是完全可以接受的行为。我可以使用一些巧妙的技巧让编译器进行重复吗?实际上,只要我知道等级,并且具有每个维度的长度,构造函数就可以是通用的:

{
    int nElements = m_dimensions[0];
    for ( int i=1 ; i<m_rank ; ++i )
        nElements *= m_dimensions[i];
    m_data = new valType[nElements];
}

编辑:请注意,访问者也需要类似的操作。

我还考虑过构造函数的选项,如下所示:

NDimensionalArray( const NDimensionalArray<int,1>& dimensions );

可以这样使用:

NDimensionalArray<int,1> dimVec(2); // Need a specification for 1-dimensional arrays.
dimVec(0) = 10;
dimVec(1) = 10;
NDimensionalArray<double,2> matrix(dimVec);

这将是一个可行的解决方案,但与我想要的用途相比,它很难看。访问多维数组也会变得非常痛苦,并且必须为每次访问构建一个维度向量非常缓慢。

【问题讨论】:

  • 出于好奇,难道boost::multi_array 完全不需要你编写这样一个类就可以做你想做的事吗?
  • 确实会。这更像是一个“我想知道我怎么能做到”的项目。你知道的好奇心。

标签: c++


【解决方案1】:

好的,我已经玩了一段时间了。这是一些模板元编程黑客,可以做一些接近你想要的事情。它允许您内联指定所有维度,它不执行任何动态内存分配或其他类似的事情。此外,使用良好的 C++ 编译器(我使用 VC++ /O2 选项进行了测试),代码将被完全内联,没有复制(事实上,对我来说,它在调用点内联了整个 NDimensionalArray 构造函数)。它将在编译时完全进行类型检查,并且不会让您传递太少或太多的维度。并且它可以重复用于索引器。如下:

template<class T, int N>
class value_pack : private value_pack<T, N-1>
{
public:

    enum { size = N };

    value_pack(value_pack<T, N-1> head, const T& tail)
        : value_pack<T, N-1>(head)
        , value(tail)
    {
    }

    value_pack<T, N+1> operator() (const T& tail) const
    {
        return value_pack<T, N+1>(*this, tail);
    }

    template<int I>
    const T& get() const
    {
        return this->value_pack<T, I+1>::value;
    }

protected:

    const T value;
};

template<class T>
struct value_pack<T, 0>
{
};

struct
{
    template <class T>
    value_pack<T, 1> operator() (const T& tail) const
    {
        return value_pack<T, 1>(value_pack<T, 0>(), tail);
    }
} const values;


template <class ValType, int Rank>
struct NDimensionalArray
{
    NDimensionalArray(value_pack<ValType, Rank> values)
    {
        // ...
    }
};


int main()
{
    NDimensionalArray<int, 3> a(values(1)(2)(3));
}

【讨论】:

  • +1 非常棘手,先生。递归模板编程,绝对可以工作,并且可能是最快的解决方案。
【解决方案2】:

我认为最好的解决方案是采用整数向量并让构造函数根据模板参数“rank”对其进行验证。

NDimensionalArray matrix(std::vector<int> matrixDimensions) 
{
    if (matrixDimensions.size() != rank) 
    {
        throw SomeException();
    }

    ...
}

我认为这里没有任何编译器技巧可以替代。 (除了 perhps 使用宏,如果你能想到一些东西,虽然严格来说这不是 编译器 把戏。)

【讨论】:

  • 是的,我很担心这一点,请参阅上面的编辑。我真的希望找到一个很好的干净解决方案,可以让课程“自然”使用。当我们考虑访问器时,这确实成为一个问题。我的意思是构造函数中的向量是一回事,但是如果我们每次都必须使用一个向量,那么访问一个包含 100 万个元素的数组将会非常慢。
  • @DeusAduro 如果您事先指定大小并访问对处理器缓存友好的元素,这并不慢。我看不出有什么办法让它与众不同,因为你需要在堆上分配一个数组,而不是在堆栈上
  • 你是对的。在这种情况下,请使用可变参数 thing。在构造函数和访问器/修改器中都进行验证。这里的问题是您不能“模板化”函数的参数数量。
  • @Edison 我认为它会很慢,并且在这种情况下它与缓存友好无关,事实上我必须为每次访问构建一个向量或其他一些索引器,这些对象本质上是临时的,我进行访问然后丢弃它。现在我可以实现更复杂的方案,其中索引器可以沿着数组的特定维度进行迭代......我正在寻找一个效率适中的简单接口。
【解决方案3】:

不是直接的答案,但请查看 blitz 库。

【讨论】:

    【解决方案4】:

    按照目前的标准化,在 C++ 中没有很好的方法。在 C++0x 中,您将能够使用模板参数包来近似(我认为我的语法正确,但不确定 requires 中的扩展):

    template <class ValType, int Rank>
    struct NDimensionalArray
    {
        template <class... Args>
        requires std::SameType<Args, ValType>... && std::True<sizeof...(Args) == Rank>
        NDimensionalArray(Args... values)
        {
            ...
        }
    };
    

    【讨论】:

    • 听起来是个很酷的功能,语法有点超出我的理解,没有一个更简单的例子啊,但是很酷。
    【解决方案5】:

    您可以使用 std::tr1::array。嗯:

    #include <array>
    
    template< class valType, int rank >
    class NDimensionalArray
    {
    public:
       NDimensionalArray(const std::tr1::array<rank>& dims);
       // ...
    };
    
    NDimensionalArray<double,2> foo({10,10});
    
    NDimensionalArray<double,2> bar({10}); // second dimension would be 0
    
    NDimensionalArray<double,1> baz({10,10}); // compile error?
    

    我实际上不确定这是否有效!我会通过 Comeau 运行它。

    已编辑根据 cmets,这种方法看起来更像:

    std::tr1::array<2> dims = {10, 10};
    NDimensionalArray<double,2> foo(dims);
    

    【讨论】:

    • 这行不通,因为聚合初始化器只在初始化变量时才被允许,而不是在任意表达式中(例如函数和构造函数参数)。
    • 无法让 Comeau 找到 tr1 数组标题,但现在你这么说,我想我记得它过去咬过我。
    • 不要引用我的话,但我相信这将是有效的 C++0x 代码,因为初始化列表变为完整的类型(在这种情况下为 std::initializer_list): en.wikipedia.org/wiki/C%2B%2B0x#Initializer_lists
    • 还有:NDimensionalArrayfoo( (std::tr1::array){10,10} );可以做到这一点(它与 g++ >= 4.0)
    【解决方案6】:

    Boost 有一个multi-array 库,它使用自定义对象来构造它们的多维数组。这是一个非常好的方法;我建议你研究(或者更好的是,使用)他们的代码。

    【讨论】:

    • 有趣的东西。我当然可以使用他们的课程,但实施这种事情总是有教育意义的。我有一些使用 va_list ( ... ) 的东西,但它似乎......脆弱?不太确定这个词看起来不是很健壮。
    • 任何使用 C 可变参数的解决方案都不是类型安全的,因为无法限制传递给函数的参数类型或数量,也无法在运行时验证其正确性。
    猜你喜欢
    • 1970-01-01
    • 2019-10-08
    • 2011-05-24
    • 1970-01-01
    • 2011-06-08
    • 2016-11-25
    • 1970-01-01
    • 2016-09-12
    相关资源
    最近更新 更多