Via stackoverflow threads like this one, I discovered that you could use an array of pointers to manage a 2D array. In the past I used to use pointer to pointer to store 2D arrays, but now I have a requirement to store my data in contiguous memory, so pointer to pointer format doesn't work anymore.
A raw 2D array is an alternative to array of pointers, but raw 2D array doesn't work for me because I want to be able to allocate storage in the heap and not on the stack because my container can be very large and I might encounter stack overflow if I use vanilla 2D array.
My question is about how memory is allocated for array of pointers. I use new
operator like below (see my constructor) to allocate my array of pointers
#include <iostream>
using namespace std;
template<typename DataType, unsigned numRows, unsigned numCols>
class Container2D {
public:
Container2D() {
m_data = new DataType[numRows][numCols];
}
~Container2D() {
delete [] m_data;
}
DataType* getData() { return &m_data[0][0]; }
private:
DataType (*m_data)[numCols];
};
int main() {
Container2D<int, 3, 3> container;
return 0;
}
Does new DataType[numRows][numCols]
allocate the entire 2D array on the heap or does it allocate numRows
pointers on the heap while allocating numCols
objects of type DataType on the stack?
In a pointer to pointer scenario (where I'd define my storage as DataType** m_data
), I know for a fact that both dimensions of my array are allocated on the heap and I would call delete m_data[i]
for each column and then call delete[] m_data
to free my row data. In the array of pointers scenario, I'm not sure if my destructor above is freeing up data correctly.
CodePudding user response:
Based on your requirement on contiguous storage, a better practice is to use a 1D contiguous array and implement the 2D index to 1D index mapping instead:
#include <iostream>
#include <cassert>
using namespace std;
template<typename DataType, unsigned numRows, unsigned numCols>
class Container2D {
public:
Container2D() { m_data = new DataType[numRows * umCols]; }
~Container2D() { delete [] m_data; }
inline DataType operator()(unsigned i, unsigned j) const {
assert( 0 <= i && i < numRows);
assert( 0 <= j && j < numCols);
return m_data[i*numCols j];
}
inline DataType& operator()(unsigned i, unsigned j) {
// same as above, but this allows inplace modifications
assert( 0 <= i && i < numRows);
assert( 0 <= j && j < numCols);
return m_data[i*numCols j];
}
private:
DataType* m_data;
};
int main() {
Container2D<int, 3, 3> container;
int x = container(0,0); // retrieve the element at (0,0);
container(1,2) = 9; // modify the element at (1,2);
// int y = container(3,0); // triggers assertion errors for out-of-bound indexing
return 0;
}
A smaller note: if numRows
and numCols
do not change for a specific class instance, new
and delete
are not necessary in this case. If they do dynamically change, it is better to store them as member variables instead of template parameters.
CodePudding user response:
Does new DataType[numRows][numCols] allocate the entire 2D array on the heap or does it allocate numRows pointers on the heap while allocating numCols objects of type DataType on the stack?
When you write
DataType arr[numRows][numCols];
the memory will be in one contiguos block as you mentioned. Nothing changes there when you use new
. It will allocate one contiguos block of memory of the specified type. There is no hidden array of pointers into the real data.