C++中现有内存的多维数组

3

我不是在讨论固定大小的指针问题,也不涉及指针存储方式,请注意这与thisthis不同。我的问题是编译器是否能够自动化手动函数。

根据此Stack Overflow问题,多维数组是按顺序存储的。

// These arrays are the same
int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}}; 
int array2[6] = { 0, 1, 2, 3, 4, 5 }; 

然而,我正在尝试在预分配的内存中创建一个包含浮点数的二维数组:
float a[5][10] 
float b[50]; // should be same memory

然后我尝试:

vector<char> x(1000);
float** a = (float**)x.data();
a[0][1] = 5;

显然,上述代码崩溃了,因为编译器不知道要分配多大的数组内存,就像第一个示例中编译器已知的数组一样。

有没有一种方法告诉编译器在连续的内存中分配多维数组,而不需要手动计算指针(例如,通过手动移动索引并调用放置新元素来实现)?

目前,我正在手动完成这项工作,例如:

template <typename T> size_t CreateBuffersInMemory(char* p,int n,int BufferSize)
{
    // ib = T** to store the data
    int ty = sizeof(T);

    int ReqArraysBytes = n * sizeof(void*);
    int ReqT = ReqArraysBytes * (ty*BufferSize);
    if (!p)
        return ReqT;

    memset(p, 0, ReqT);
    ib = (T**)p;
    p += n * sizeof(void*);
    for (int i = 0; i < n; i++)
    {
        ib[i] = (T*)p;
        p += ty*BufferSize;
    }
    return ReqT;
}

非常感谢。


可以,但是我能让编译器像静态数组一样自动初始化数组吗? - Michael Chourdakis
4
访问 float** 的语法是相同的:a[i][j],但访问真正的多维数组的语义完全不同。 float** 是指向指针的指针。因此,第一维应该包含指向行的指针。 - rustyx
1
可能是如何使用new在C++中声明二维数组?的重复问题。 - rustyx
2
@rustyx:我不认为这完全是一个重复的问题,但理解float **float [][]的工作原理可能有助于回答这个问题。也许你可以简单地参考一下那篇帖子? - andreee
可能是重复的问题:如何将简单指针转换为固定大小的多维数组? - drescherjm
但是,如果编译器可以自动化手动函数,答案是它不能在 https://dev59.com/G7Pma4cB1Zd3GeqPliEf#55594599 所做的范围之外。 - drescherjm
4个回答

3

T [rows] [cols]数组分配为一维数组,请分配T [rows * cols]

要访问该一维数组的元素[i] [j],您可以执行p [i * cols + j]

例如:

template<class T>
struct Array2d {
    T* elements_;
    unsigned columns_;

    Array2d(unsigned rows, unsigned columns)
        : elements_(new T[rows * columns]{}) // Allocate and value-initialize.
        , columns_(columns)
    {}

    T* operator[](unsigned row) {
        return elements_ + row * columns_;
    }

    // TODO: Implement the special member functions.
};

int main() {
    Array2d<int> a(5, 10);
    a[3][1] = 0;
}

这样做是可行的,但现有的代码将通过p[i][j]访问元素[i][j],而不是[i*n + j]。 - Michael Chourdakis
@MichaelChourdakis 给你添加了一个例子。 - Maxim Egorushkin

2
你的代码会导致未定义行为,因为x.data()指向的不是指针数组,而是1000个char类型的对象数组。你应该庆幸它崩溃了... ;-)
一种访问连续缓冲区的多维数组的方法是拥有另一个表示多维视图的对象。这个视图对象可以提供成员函数来使用多维索引访问数据。为了实现a[i][j][k]这样的语法(你似乎是想要的),需要提供重载的[]运算符,它返回一个代理对象,该代理对象本身提供operator [],以此类推,直到到达单个维度。
例如,对于在编译时固定维度的情况,我们可以定义:
template <int Extent, int... Extents>
struct row_major_layout;

template <int Extent>
struct row_major_layout<Extent>
{
    template <typename T>
    static auto view(T* data) { return data; }
};

template <int Extent, int... Extents>
struct row_major_layout
{
    static constexpr int stride = (Extents * ... * 1);

    template <typename T>
    class span
    {
        T* data;

    public:
        span(T* data) : data(data) {}

        auto operator[](std::size_t i) const
        {
            return row_major_layout<Extents...>::view(data + i * stride);
        }
    };

    template <typename T>
    static auto view(T* data) { return span<T>(data); }
};

然后只需创建并访问这样的row_major_layout视图。最初的回答。
void test()
{
    constexpr int M = 7, N = 2, K = 5;

    std::vector<int> bla(row_major_layout<M, N, K>::size);

    auto a3d = row_major_layout<M, N, K>::view(data(bla));

    a3d[2][1][3] = 42;
}

"最初的回答"可以翻译为"Original Answer"。以下是需要翻译的内容:

template <int D> class row_major_layout; template <> class row_major_layout<1> { public: row_major_layout(std::size_t extent) {} static constexpr std::size_t size(std::size_t extent) { return extent; } template <typename T> friend auto view(T* data, const row_major_layout&) { return data; } }; template <int D> class row_major_layout : row_major_layout<D - 1> { std::size_t stride; public: template <typename... Dim> row_major_layout(std::size_t extent, Dim&&... extents) : row_major_layout<D - 1>(std::forward<Dim>(extents)...), stride((extents * ... * 1)) { } template <typename... Dim> static constexpr std::size_t size(std::size_t extent, Dim&&... extents) { return extent * row_major_layout<D - 1>::size(std::forward<Dim>(extents)...); } template <typename T> class span { T* data; std::size_t stride; const row_major_layout<D - 1>& layout; public: span(T* data, std::size_t stride, const row_major_layout<D - 1>& layout) : data(data), stride(stride), layout(layout) { } auto operator[](std::size_t i) const { return view(data + i * stride, layout); } }; template <typename T> friend auto view(T* data, const row_major_layout& layout) { return span<T>(data, layout.stride, layout); } };

并且

void test(int M, int N, int K)
{
    std::vector<int> bla(row_major_layout<3>::size(M, N, K));

    auto a3d = view(data(bla), row_major_layout<3>(M, N, K));

    a3d[2][1][3] = 42;
}

"最初的回答"


1
基于这个答案,假设你想要一个char数组,你可以这样做
std::vector<char> x(1000);
char (&ar)[200][5] = *reinterpret_cast<char (*)[200][5]>(x.data());

然后你可以像使用普通的二维数组一样使用ar,例如:

char c = ar[2][3];

1
这种方法的一个问题是2D数组必须在编译时固定大小,因为你需要将其转换为固定类型。这将不像矩阵类那样灵活。 - drescherjm
我认为上面的代码存在未定义行为。指向数组和指向其第一个元素的指针不是可互换的指针。此外,只要ar所引用的数组的元素类型不是charunsigned charstd::byte之一,我也会认为在这里违反了严格别名规则。我认为没有人应该这样做... - Michael Kenzel

1

对于任何试图实现相同目标的人,我已经创建了一个可变参数模板函数,它可以在现有内存中创建一个n维数组:

template <typename T = char> size_t CreateArrayAtMemory(void*, size_t bs)
{
    return bs*sizeof(T);
}

template <typename T = char,typename ... Args>
size_t CreateArrayAtMemory(void* p, size_t bs, Args ... args)
{
    size_t R = 0;
    size_t PS = sizeof(void*);
    char* P = (char*)p;
    char* P0 = (char*)p;

    size_t BytesForAllPointers = bs*PS;
    R = BytesForAllPointers;

    char* pos = P0 + BytesForAllPointers;
    for (size_t i = 0; i < bs; i++)
    {
        char** pp = (char**)P;
        if (p)
            *pp = pos;
        size_t RLD = CreateArrayAtMemory<T>(p ? pos : nullptr, args ...);
        P += PS;
        R += RLD;
        pos += RLD;
    }
    return R;
}

用法:

创建一个2x3x4的字符数组:

int j = 0;
size_t n3 = CreateArrayAtMemory<char>(nullptr,2,3,4);
std::vector<char> a3(n3);
char*** f3 = (char***)a3.data();
CreateArrayAtMemory<char>(f3,2,3,4);
for (int i1 = 0; i1 < 2; i1++)
{
    for (int i2 = 0; i2 < 3; i2++)
    {
        for (int i3 = 0; i3 < 4; i3++)
        {
            f3[i1][i2][i3] = j++;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接