Является ли новый 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;
};