Является ли новый int [] [] правильной вещью в С++?

Я столкнулся с некоторым кодом, который выделяет массив 2d со следующим подходом:

auto a = new int[10][10];

Является ли это правильной вещью в С++? У меня есть поиск по нескольким справочным материалам на С++, ни один из них не упомянул о таком подходе. Обычно я бы сделал выделение вручную следующим образом:

int  **a = new int *[10];
for (int i = 0; i < 10; i++) {
    a[i] = new int[10];
}

Если первый подход действительно, то какой из них предпочтительнее?

Ответы

Ответ 1

Первый пример:

auto a = new int[10][10];

Это выделяет многомерный массив или массив массивов как непрерывный блок памяти.

Второй пример:

int** a = new int*[10];
for (int i = 0; i < 10; i++) {
    a[i] = new int[10];
}

Это не истинный многомерный массив. Это, по сути, массив указателей и требует двух указаний на доступ к каждому элементу.

Ответ 2

Выражение new int[10][10] означает выделение массива из десяти элементов типа int[10], так что да, это действительная вещь.

Тип указателя, возвращаемого новым, int(*)[10]; можно объявить переменную такого типа через int (*ptr)[10];.

Ради удобочитаемости, вероятно, не следует использовать этот синтаксис, и он должен использовать auto как в вашем примере, или использовать typedef для упрощения, как в

using int10 = int[10]; // typedef int int10[10];
int10 *ptr;

Ответ 3

В этом случае для небольших массивов более эффективно выделять их в стеке. Возможно, даже используя удобную обертку, такую ​​как std::array<std::array<int, 10>, 10>. Однако, в общем случае, можно сделать что-то вроде следующего:

auto arr = new int[a][b];

Где a - std::size_t, а b - constexpr std::size_t. Это приводит к более эффективному распределению, так как в качестве аргумента должен быть только один вызов operator new[] с sizeof(int) * a * b вместо a вызовов operator new[] с sizeof(int) * b в качестве аргумента. Как заявил Галик в своем ответе, существует также возможность ускорить время доступа из-за увеличения когерентности кеша (весь массив смежен в памяти).

Однако, единственная причина, по которой я могу предположить, что кто-то использует что-то вроде этого, будет с матрицей/тензором размера времени компиляции, где все измерения известны во время компиляции, но он выделяет кучу, если она превышает размер стека.

В целом, вероятно, лучше всего написать свой собственный класс оболочки RAII, например, следующим образом (вам также нужно будет добавить различные средства доступа для высоты/ширины, а также реализовать конструктор и назначение copy/move, но общая идея здесь

template <typename T>
class Matrix {
public:
     Matrix( std::size_t height, std::size_t width ) : m_height( height ), m_width( width )
     {
           m_data = new T[height * width]();
     }

     ~Matrix() { delete m_data; m_data = nullptr; }

public:
     T&               operator()( std::size_t x, std::size_t y )
     {
          // Add bounds-checking here depending on your use-case
          // by throwing a std::out_of_range if x/y are outside 
          // of the valid domain.
          return m_data[x + y * m_width];
     }

     const T&         operator()( std::size_t x, std::size_t y ) const
     {
          return m_data[x + y * m_width];
     }

private:
     std::size_t      m_height;
     std::size_t      m_width;
     T*               m_data;
};