表示为一维的多维数组(模板化为 n 维)

Posted

技术标签:

【中文标题】表示为一维的多维数组(模板化为 n 维)【英文标题】:Multidimensional arrays represented as 1D (templatised for n dimensions) 【发布时间】:2018-05-05 12:50:39 【问题描述】:

在 C++ 中声明具有静态大小的多维数组非常容易,然后将数组存储在一个连续的内存块中(行主要布局)。

问题

然而,正如in other SO thread regarding arrays 所讨论的那样,在 C++ 中声明动态分配的多维数组(大小仅在运行时知道)非常棘手。要使用多个方括号(在二维数组的情况下)保留相同的语法,您需要创建一个指针数组,指向另一组数组(行)。随着维度的增加,它会增加更多(不必要的)间接级别、内存碎片,并且对于较小的数组,指针可能会占用比实际数据更多的内存。

其中一种解决方案是使用一维数组,然后重新计算索引。

示例:

大小为 10、3 和 5 的 3D 数组。我想要一个位于位置 3、1、4 的元素,而不是写3darray[3][1][4] 我会写3darray[index],其中索引将计算为3*(y_dym_size*z_dym_size) + 1*(z_dym_size) + 4,当被替换时, 结果到3*(3*5)+1*(5)+4

我可以轻松地创建一个封装动态分配的数组并以呈现的方式在索引中重新计算的类,但这并不实用,因为它需要针对每个维数编写。

问题:

我想创建一个模板,该模板适用于任意数量的维度且开销为零(这是现代 C++ 的精神 - 具有可重用的代码/类,其中更多的工作被转移到编译器)。我有以下适用于 n 维数组的代码,但是没有 0 开销。它包含 for 循环,并且还有一个用于 1D 分辨率的数组:

template <class T, size_t DIM>
class arrayND
    std::array<size_t, DIM> sizes;
    std::array<size_t, DIM-1> access_multiplier;
    vector<T> data;

  public:
    using iterator = typename vector<T>::iterator;
    using const_iterator = typename vector<T>::const_iterator;

    template <typename... Args, typename std::enable_if_t<sizeof...(Args) == DIM, int> = 0>
    arrayND(Args&&... args) 
        std::array<size_t, DIM> tempargs...;
        sizes = temp;
        size_t mult = 1;
        for(int i = DIM-2; i >= 0; --i)
            mult *= sizes[i+1];
            access_multiplier[i] = mult;
        
        data.resize(mult*temp[0]);
    

    template <typename... Args, typename std::enable_if_t<sizeof...(Args) == DIM, int> = 0>
    T& get(Args&&... args)
        std::array<size_t, DIM> idx_copyargs...;
        size_t index = idx_copy[DIM-1];
        for(int i = DIM-2; i >= 0; --i)
            index += idx_copy[i]*access_multiplier[i];
        
        return data[index];
    

    template <typename... Args, typename std::enable_if_t<sizeof...(Args) == DIM, int> = 0>
    T& operator()(Args&&... args)
        return get(args...);
    

    void set(const T& elem)
        fill(begin(data), end(data), elem);
    

    iterator begin()
        return begin(data);
    

    iterator end()
        return end(data);
    

    const_iterator begin() const
        return cbegin(data);
    

    const_iterator end() const
        return cend(data);
    
;

我正在考虑的其他方法是使用可变参数模板,希望在编译器优化之后与专门为某些维度编写的代码相同:

int getIndex(size_t index)
    return index;


template<typename... Args>
int getIndex(size_t index, Args... args)
    return access_multiplier[DIM-sizeof...(Args)-1]*index + getIndex(args...);


template <typename... Args, typename std::enable_if_t<sizeof...(Args) == DIM, int> = 0>
T& get(Args&&... args)
    return data[getIndex(args...)];
    /*std::array<size_t, DIM> idx_copyargs...;
    size_t index = idx_copy[DIM-1];
    for(int i = DIM-2; i >= 0; --i)
        index += idx_copy[i]*access_multiplier[i];
    
    return data[index];*/

在当前版本 (C++17) 或 C++ 语言中是否有办法同时获得灵活性(任意维数)和性能(零开销与专门为某些维数编写的代码相比)?如果必须有开销,那么对它进行硬编码更有意义,比如最多 5 个维度。 一些现有的库中是否已经实现了动态多维数组?

【问题讨论】:

静态大小的数组不需要向量。并且有很多方法可以使用[] 来实现它们。要么您感到困惑,要么存在需要您进行体操的限制。没有明确的约束很难解决问题。 @Yakk:他想成为abe来声明arrayND&lt;3&gt; small(2,2), big(200,200); - 维度的数量是类型的一部分,但是每个维度的大小是传递给构造函数的。这需要一个向量(或其他动态分配的缓冲区)。 感谢 Martin 提供的示例,正​​如您所写的那样。 @yakk我已经更新了这个问题,明确指出这个问题是关于动态分配的数组(我错过了)。 【参考方案1】:

从存储中拆分视图。

T 的 n 维数组视图是一个类,它有一个指向 T 的指针以及一些获取 n-1 步幅大小的方法。 [] 返回一个 n-1 维数组视图。

这种观点有两种不同的风格。第一个存储步幅,第二个存储步幅的连续缓冲区的指针。两者各有优势;当某些或所有维度固定时,第一个小心甚至可以优化。但我会做第二个。

template<class T, std::size_t N>
struct slice 
  T* ptr=0;
  std::size_t const* strides=0;
  slice<T,N-1> operator[]( std::size_t i )const
    return  ptr + i**strides, strides+1 ;
  
;
template<class T>
struct slice<T,1> 
  T* ptr=0;
  std::size_t const* strides=0;
  T& operator[]( std::size_t i )const
    return *(ptr + i**strides);
  
;

这个允许每个元素跨步。

现在你只需要暴露一个stride&lt;T,N&gt; 来做链式[]。这类似于我为 3 维编写它的方式。

如果您更喜欢(x,y,z) 语法并且您唯一的问题是 for 循环并且担心编译器没有将其展平,您可以使用包扩展将其编写为强制展平。但首先要分析和检查优化的装配。

【讨论】:

以上是关于表示为一维的多维数组(模板化为 n 维)的主要内容,如果未能解决你的问题,请参考以下文章

python如何减小维度

将多维数组的列格式化为一维数组

C#创建2维数组

TensorFlow入门

numpy中多维数组的绝对索引

多维数组与指针