-1

I don't understand how to create an multidimensional array on the heap with the new operator. In java it works like that:

int[][][] array = new int[3][3][3];

how do I achieve the same thing in c++? I wanna have an 3d array that I can access like that:

array[0][0][0] = 1;

How can I do that?

Reskareth
  • 47
  • 4
  • 4
    Does this answer your question? [How do I declare a 2d array in C++ using new?](https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new) Not exactly the question at hand, but the answer should be more than sufficient. [This](https://stackoverflow.com/a/936709/10055166) answer in particular would be very useful. – Lala5th Apr 26 '22 at 18:36
  • 2
    Note that this is often a horrible way to make a multidimensional array because it results in arrays of pointers pointing to arrays resulting in a lot of pointer-chasing and poor cache use. You are often better off allocating a single, large block of memory and performing the indexing math yourself. – user4581301 Apr 26 '22 at 18:44
  • @user4581301 so why close the question with an inadequate duplicate? – n. m. could be an AI Apr 26 '22 at 18:48
  • Because my read of the question was that the asker *wanted* the horrible solution. The sane C++ programmer wouldn't involve `new` at all. – user4581301 Apr 26 '22 at 18:50
  • @user4581301 A user that has no idea about what exists out there wants dangerous and inadequate things. You don't give them what they want, you tell them what to want, or just move along. – n. m. could be an AI Apr 26 '22 at 18:55
  • user4581301 Well I thought that I once heard that the stack if limited in terms of size and that you have to use new if your variable is bigger than the maximum size of the stack (But as I said, I'm a beginner so I might have misunderstootd that. So the preferred way is to use a single dimensional array? – Reskareth Apr 26 '22 at 18:58
  • 1
    [Here is a a really simple approach to a 2D matrix](https://stackoverflow.com/a/2076668/4581301). If you're forced to use `new` because of assignment restrictions, [here is a more complicated version](https://isocpp.org/wiki/faq/operator-overloading#matrix-subscript-op) where you have to manually manage the allocation. If you want to use the `[]` operators, you can still get that syntax, but it typically requires [a chain of proxy objects](https://stackoverflow.com/a/2216055/4581301). – user4581301 Apr 26 '22 at 19:02
  • It looks like [`std::valarray`](https://en.cppreference.com/w/cpp/numeric/valarray) still exists; this is a class in the C++ standard library that can be used for a multidimensional array, but it's often ignored because it can be horribly complicated. – Spencer Apr 26 '22 at 19:06
  • 1
    @Reskareth you're not far from reality. Automatic storage is fast, but size-limited and precious. Dynamic storage is slow on acquisition and relinquish, but no different speed-wise on access *unless* you do something like layer pointers on pointers (on pointers oh my!) to "fake" the dimensions. Take a look at that simple 2D implementation from user4581301. It has teeth, and is a decent blueprint for at-least familiarizing yourself with some options. – WhozCraig Apr 26 '22 at 19:08

2 Answers2

1

Generally as you mentioned in the comments stack space is limited. In a lot of cases this does not really matter and most definitely it is not a problem for an int array that essentially has size in the double digits. To have it on the stack you can just do:

int array[3][3][3];

Note that this only works if 3 is known compile time. This has the benefit that it is incredibly fast and not really much trouble for you the programmer.

The second option is actually allocating on the heap. The naive way of doing this is:

int*** array = new int**[3];
for (size_t i = 0; i < 3; ++i) {
    array[i] = new int*[3];
    for (size_t j = 0; j < 3; ++j)
        array[i][j] = new int[3];
}

with of course delete[], when we are done with the memory like:

for (size_t i = 0; i < 3; ++i) {
    for (size_t j = 0; j < 3; ++j)
        delete[] array[i][j];
    delete[] array[i];
}
delete[] array;

The good thing about this is that 3 here does not have to be compile time, but this suffers greatly from the slowness of new. This second problem can be offset by using a trick like:

int* data = new int[3*3*3];
int** helper_array = new int*[3*3];
int*** array = new int**[3];
for (size_t i = 0; i < 3; ++i)
    array[i] = helper_array + 3*i;
for (size_t i = 0; i < 3; ++i)
for (size_t j = 0; j < 3; ++j)
    helper_array[3*i + j] = data + 3*3*i + 3*j;

with the obligatory release of memory:

delete[] array;
delete[] helper_array;
delete[] data;

This is much easier and nicer to handle, and it also suffers less from the slow speed of new, while still retaining the array[i][j][k] data access.

The easiest way however to handle dynamic memory is through some container like std::vector<>:

std::vector<std::vector<std::vector<int>>> array(3, std::vector<std::vector<int>>(3, std::vector<int>(3)));

This has the benefits that the memory is no longer managed by you, but it still allows for data that has length unknown at compile time. It also does not store data on the stack (unless you explicitly make it do that), so you don't have to worry about stack space running out, but it will have the slow down of the int*** array = new int**[3] solution. An other drawback is that this solution can become very messy at multiple nested vectors.

While the solutions presented here are perfectly valid for some other solutions involving more modern approaches to C++ see n. 1.8e9-where's-my-share m.'s answer. Those don't open up the possibility of memory leaks and usually are much more nice to manage them because of this.

Lala5th
  • 1,137
  • 7
  • 18
0

For a known-constant-size arrays you can do this

auto shared_array = std::shared_ptr<int[3][3][3]>();

If only the first dimension is not a compile-time constant, you can do this:

auto shared_array = std::shared_ptr<int[][3][3]>(N); // N is a variable

(Caution: you need a cutting edge version of gcc or clang to compile this, and at least -std=c++20; the latest msvc /std:c++latest supports this)

(There is also a unique_ptr version of the second variant, but not of the first variant. Why is it so is beyond the scope of this answer).

If a dimension other than the first is not a compile-time constant, then things get really complicated. Unlike Java, C doesn't have true multidimensional arrays. What to do?

One can use a multi-level std::vector in a pinch:

auto multi_vector = std::vector<std::vector<std::vector<int>>>();
multi_vector.resize(N);
for (auto& elem: multi_vector)
{
   elem.resize(M);
   for (auto& next_level_elem: elem)
   {
      next_level_elem.resize(K);
   }
}

You can tell this is not a most pretty solution, but write it once, hide it in a function, and forget about it. You can also write something like this to the same effect:

auto array = 
    std::vector<std::vector<std::vector<int>>>(N, 
      std::vector<std::vector<int>>(M,
        std::vector<int>(K)));

which may or may not be more readable, depending on who's reading (it's less readable for me but YMMV).

You can also use an array of unique_ptrs to arrays in much the same way but I'll leave it to you to figure it out.

The real problem with this solution is not that it is ugly, but that it is very, very inefficient. The triple indirection is a cache killer.

The real solution is to use a dedicated third-party multidimensional array library like Boost.MultiArray or Eigen.Tensor (note this is unsupported). Or, when you are a really seasoned C++ programmer, roll your own.

#include <boost/multi_array.hpp>
using boost3arrType = boost::multi_array<int, 3>;
using boost3shapeType = boost::array<boost3arrType::index, 3>;
auto x = boost3arrType(boost3shapeType{{2,3,4}});
x[0][1][2] = 42;

#include <eigen3/unsupported/Eigen/CXX11/Tensor>
auto eigenTensor3 = Eigen::Tensor<int, 3>(2, 3, 4);
eigenTensor3(0,1,2) = 42;

No support for eigenTensor3[0][1][2] syntax.

Finally, if you are enrolled in a really crappy C++ course and your professor would not accept anything he couldn't find in the 1990 Stroustrup book, there's always this. You should not use this method for anything except your assignment that disallows all of the above.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243