Вместо использования многомерных массивов с указателями на указатели, можно воспользоваться вложенными друг в друга стандартными контейнерами std::vector, которые обеспечивают динамическое управление памятью.
Вот как можно определить двумерный массив:
std::vector<std::vector<int>> array(ROWS, std::vector<int>(COLUMNS));
Здесь видно, что при указании размерности массива, первой размерностью указываются строки (ROWS), затем столбцы (COLUMN) в качестве второй размерности. Это не более чем условность, и до тех пор, пока не используется синтаксис инициализации (см. далее) -  что считать строками, а что столбцами - это личное дело программиста. На данном этапе достаточно понять, что даже во вложенном синтаксисе размеры массива перечисляются слева-направо (а не наоборот и не по спирали).
Пример обращения к элементам многомерного массива через квадратные скобки выглядит так:
#include <iostream>
#include <vector>
int main() {
    // Создание и инициализация двумерного вектора
    std::vector<std::vector<int>> array = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    // Обращение к элементам массива на чтение
    std::cout << "Element at (0, 0): " << array[0][0] << std::endl;
    std::cout << "Element at (1, 2): " << array[1][2] << std::endl;
    std::cout << "Element at (2, 1): " << array[2][1] << std::endl;
    // Обращение к элементам массива на запись
    array[1][1]=10;
    std::cout << "Element at (1, 1): " << array[1][1] << std::endl;
    return 0;
}
Результат работы вышеприведенного кода:
Element at (0, 0): 1
Element at (1, 2): 6
Element at (2, 1): 8
Element at (1, 1): 10
По этим значениям видно, что первый индекс указывает на строку, второй - на столбец. Это нужно учитывать, чтобы понимать, какие ячейки адресуются соответствующими индексами относительно синтаксиса инициализации многомерного массива начальными значениями.