2

I have the following to code to allocate the 3d array:


int ***allocate3DArray(int y, int x, int r) {
    int ***array = (int ***) malloc(sizeof(int **) * y);
    for (int i = 0; i < y; i++) {
        array[i] = (int **) malloc(sizeof(int *) * x);
        for (int j = 0; j < x; j++) {
            array[i][j] = (int *) malloc(sizeof(int) * r);
            for (int k = 0; k < r; k++) {
                array[i][j][k] = 0;
            }
        }
    }
    return array;

}


void free3d(int ***arr, int y, int x, int r) {
    for (int i = 0; i < y; i++) {
        for (int j = 0; j < x; j++) {
            free(arr[i][j]);
        }
        free(arr[i]);

    }
    free(arr);

}

This seems a bit clunky and it's difficult to do error-handling if I malloc fails.

That said, it's quite ergonomic because I can simply access an element like so: int a = arr[x][y][z]

What would be the best way to initialize a 3d array that gives the same ergonomic flexibility while mitigating some of the above drawbacks?

I tried this:

    std::array < std::array < std::array < int, radius >, image.cols >, image.rows > houghSpace;

but I got an error:

 error: non-type template argument is not a constant expression
    std::array < std::array < std::array < int, radius >, image.cols >, image.rows > houghSpace;
                                                ^~~~~~
sobel.cpp:117:49: note: initializer of 'radius' is not a constant expression
sobel.cpp:116:15: note: declared here
    const int radius = image.rows / 2;
              ^
sobel.cpp:117:71: error: expected a type
    std::array < std::array < std::array < int, radius >, image.cols >, image.rows > houghSpace;
                                                                      ^
sobel.cpp:117:86: error: C++ requires a type specifier for all declarations
    std::array < std::array < std::array < int, radius >, image.cols >, image.rows > houghSpace;
                                                                                     ^

nz_21
  • 6,140
  • 7
  • 34
  • 80
  • 1
    Please refer to https://stackoverflow.com/questions/17759757/multidimensional-stdarray – tenfour Nov 20 '19 at 20:23
  • 5
    `malloc(sizeof(int) * y * x * r);`? – John Nov 20 '19 at 20:24
  • I don't think there is a definitive answer to this (opinion based) but I would consider this technique (its a 2d array but could be extrapolated to a 3d) https://stackoverflow.com/questions/53038457/what-is-the-best-modern-c-approach-to-construct-and-manipulate-a-2d-array/53038618#53038618 – Galik Nov 20 '19 at 20:24
  • 1
    If you want performance, the way to do it is like the second example [here](https://stackoverflow.com/a/58915941/4342498). Use a 1d vector and use math to pretend is has multiple dimensions. There is lots of ways to do this but there are libraries that have already done so, so you should just get one of them. – NathanOliver Nov 20 '19 at 20:24
  • Are you sure you want to ask about C++ and not C? Because you shouldn't use `malloc`/`free` in C++, instead use `new[]`/`delete[]`. Also in C++ you would idiomatically not use manual dynamic memory allocation at all, but `std::vector` instead. – walnut Nov 20 '19 at 20:24
  • Can't you use new? Isn't it safer than malloc? – Felipe Gutierrez Nov 20 '19 at 20:25
  • 2
    Even if I were to take the 3-star programmer approach like you have I would allocate the entire memory for the array as a single block and then store pointers into that contiguous block into a 2d array. – Galik Nov 20 '19 at 20:27
  • "What's the most idiomatic way to allocate a 3D array in C++?" - not with manual memory management, that's for sure. And `malloc`/`free` in a C++ program, really? Use `std::array` or `std::vector`. Or *at least* use smart pointers. This is wrong in *so many* ways it's not even funny. – Jesper Juhl Nov 20 '19 at 20:33
  • 2
    I would say the most idomatic way to allocate a 3D array is to not do it. Or in other words, use a flat 1D array and then map indices to whatever dimension you like – 463035818_is_not_an_ai Nov 20 '19 at 20:40
  • You could also [try this](https://stackoverflow.com/questions/52068410/allocating-a-large-memory-block-in-c/52069368#52069368). Note that all failures are rolled-back, unlike your attempt. – PaulMcKenzie Nov 20 '19 at 20:40
  • 1
    The answer to the article linked by @tenfour is the correct one. An idiomatic array equivalent to C's array in C++ is to use `std::array`, as it works the same way as a C array does, but has lovely zero-cost methods to access properties. An idiomatic multi-dimensional array, is, therefore, an `std::array` of `std::array`s (and so forth for all X dimensions). It won't fail due to `bad_alloc` if you define it on the stack (but you might run out of stack space). Memory's never free, alas. – parktomatomi Nov 20 '19 at 21:09
  • @John that just allocates a big contiguous array, right? I want to be able to access the array multi-dimensionally. – nz_21 Nov 20 '19 at 21:50
  • @parktomatomi don't you need to know the size statically though? – nz_21 Nov 20 '19 at 21:56
  • The ISO C++ folks have been punting on `std::make_array` for ages now: https://en.cppreference.com/w/cpp/experimental/make_array. But you can make one yourself to infer the array size from the initialization. See "possible implementation" from that link. – parktomatomi Nov 20 '19 at 21:59
  • @nz_21 Hey, you might be interested in this gsl class: https://stackoverflow.com/questions/45201524/what-is-gslmulti-span-to-be-used-for , which adds a multidimensional index over a block of contiguous data – parktomatomi Nov 21 '19 at 00:49

2 Answers2

2

You want a 3-dimensional array with a cuboid shape. What you currently have is a jagged array. Every lane can have independent size, and is allocated separately. This creates 3(4) problems:

  1. The number of allocations is x*y+1
  2. Reading any elements requires 3 indirect memory loads.
  3. Uses more storage than needed, since pointer to each allocated region needs to be kept.
  4. Your code also requires manually handling the memory

For the same reasons the solution using vectors is also bad. It only solves one problem - manual allocation and deallocation. Otherwise it's even worse because it stores more redundant data.

What you want is to abstract a contiguous memory range such that it can be interpreted as a 3d array.

#include <iostream>
#include <memory>
#include <cstdint>

template <typename T>
struct Array3
{
    Array3(size_t w, size_t h, size_t d) :
        m_data(std::make_unique<T[]>(w*h*d)),
        m_width(w),
        m_height(h),
        m_depth(d)
    {
    }

    T& operator()(size_t x, size_t y, size_t z)
    {
        return m_data[(x * m_height + y) * m_depth + z];
    }

    const T& operator()(size_t x, size_t y, size_t z) const
    {
        return m_data[(x * m_height + y) * m_depth + z];
    }

    size_t width() const
    {
        return m_width;
    }

    size_t height() const
    {
        return m_height;
    }

    size_t depth() const
    {
        return m_depth;
    }

    T* data()
    {
        return m_data.get();
    }

    const T* data() const
    {
        return m_data.get();
    }

private:
    std::unique_ptr<T[]> m_data;
    std::size_t m_width;
    std::size_t m_height;
    std::size_t m_depth;
};


int main()
{
    Array3<int> a(10, 20, 30);
    a(5, 10, 15) = 123;
    std::cout << a(5, 10, 15);
}

Access is done with operator () instead of []. It can be done with [], but requires more boilerplate code and proxy objects.

Sopel
  • 1,179
  • 1
  • 10
  • 15
0

Some strong recommendations for arrays in C++

  • You must NOT use malloc and free in C++
  • You should not even use NEW and DELETE any longer
  • You should never use raw pointers for owned memory. Please use std::unique_ptr or std::shared_ptr instead.

So, never use the functions as shown above (allocate3DArray, free3d)

Ok. So, now we want to switch to std::array and you are wondering about the error messages. They are rather clear, as shown in your example. A std::array has a static size. It is not dynamic. So you need to give a compile time constant to define the size.

What you are looking for is a std::vector. It is dynamic, can grow in size and has also an index operator.

It can be used with the index operator [] without restrictions.

Please see the following example

#include <iostream>
#include <iterator>
#include <vector>

int main()
{
    // Dimensions for the 3 dimensional vector
    size_t xSize{3};
    size_t ySize{4};
    size_t zSize{5};
    int initialValue{0};

    // Define 3 dimensional vector and initialize it.
    std::vector<std::vector<std::vector<int>>> array3d(xSize, std::vector<std::vector<int>>(ySize, std::vector<int>(zSize,initialValue)));

    array3d[1][2][3] = 42;

    std::cout << "Result: " << array3d[1][2][3] << "\n";

    return 0;
}
A M
  • 14,694
  • 5
  • 19
  • 44
  • 1
    `std::vector>(ySize, std::vector(zSize,initialValue)` This is gonna map every row to the same vector. Sadly, you need to manually create a vector for each row. – parktomatomi Nov 21 '19 at 14:32
  • 1
    @parktomatomi no, the vectors are copied, no references are being stored. I upvoted the comment by mistake and now cannot back of. – Sopel Nov 21 '19 at 14:54
  • Oh, clever. Thanks for clarifying. – parktomatomi Nov 21 '19 at 14:54