You will need to use some form of heap-storage for a user-specified "2D-array" (e.g. a vector<vector<T>>
). This is necessary since arrays have their sizes fixed at compile-time.
Because the value won't be known until runtime, there really isn't an alternative available. That said, there are two different approaches you can use with heap memory:
- Nested containers (e.g.
vector<vector<...>>
), and
- One container using arithmetic to produce a 2D "projection"
1. Nested containers
A container of a container like a std::vector<std::vector<T>>
is likely the easiest way. This is the recommended approach over manually-managed heap-allocated pointers (e.g. don't use new T*[N]
followed by a bunch of new T[M]
pointers. See 'Why should C++ programmers minimize use of 'new'?' for more details).
This can be done easily:
auto rows = std::size_t{};
auto columns = std::size_t{};
// Get the input (ignoring prompts for the sake of brevity)
std::cin >> rows;
std::cin >> columns;
// using 'T' as a placeholder for the type
auto array_2d = std::vector<std::vector<T>>{};
array_2d.reserve(rows);
// Create 'row' number of vector objects
for (auto i = 0u; i < rows; ++i) {
array_2d.push_back(); // create a new vector
array_2d.resize(columns); // resize the vector to the number of columns
}
Note that this does not create a true "2d array" -- but rather it creates a container that holds row
number of containers, each which is a container holding column
T
objects.
2. A single container, with a projection
The second way is to use a single container, such as a std::vector<T>
, but to write a wrapper that projects a 2D array over it. For example, you could have a get(row, column)
function that will access the element in the single contiguous vector
and return it. This creates only 1 contiguous chunk of objects for the vector, but it's also (slightly) more complicated.
class Array2D {
public:
Array2D(std::size_t rows, std::size_t columns)
: m_data{},
m_rows{rows},
m_columns{columns}
{
m_data.resize(rows * columns);
}
auto get(std::size_t row, std::size_t column) -> T& {
// you could also do checking here
return m_data[row * m_columns + column];
}
auto get(std::size_t row, std::size_t column) const -> const T&; // can do the same for a const-qualified one...
// Note: if you are using C++23, you can also have operator[] with
// more than one argument
// ...
private:
std::vector<T> m_data;
std::size_t m_rows;
std::size_t m_columns;
};
This would then be used like:
auto rows = std::size_t{};
auto columns = std::size_t{};
// Get the input (ignoring prompts for the sake of brevity)
std::cin >> rows;
std::cin >> columns;
auto array = Array2D{rows, columns};
// Get a reference to a value
auto& v = array.get(0,5);
// Set a value
array.get(0,5) = ...
if you want to keep an array[row][column]
syntax, you could also implement operator[]
to return a proxy object so that you could make the syntax behave more like a 2D array:
class Array2DProxy {
public:
explicit Array2DProxy(T* row) : m_row{row}{}
auto operator[](std::size_t column) -> T& {
return m_row[column];
}
private:
T* m_row;
};
class Array2D {
...
auto operator[](std::size_t row) -> Array2DProxy {
// Return a proxy object using a pointer to the start of the row
return Array2DProxy{&m_data[row * m_columns]};
}
...
}