Предпочтительный метод для "нового" указателя на массив
Почему это ошибка:
typedef int H[4];
H * h = new H; // error: cannot convert 'int*' to 'int (*)[4]' in initialization
?
Кроме того, почему это не ошибка:
H * h = new H[1];
?
Почему компилятор считает, что new H
возвращает int *
, тогда как new H[1]
возвращает H *
, как ожидалось?
Иначе говоря: , почему T * t = new T;
является правильным для общего типа T, но неверно, когда T
является типом массива?
Каким будет канонический способ выделения простого типа массива, например, через new
?
Обратите внимание, что это упрощенный пример, так, например, new int[4]
не является приемлемым обходным путем - мне нужно использовать фактический тип из предыдущего typedef
.
Заметьте также, что я знаю, что использование std::vector
, std::array
и др. обычно предпочтительнее для массивов в стиле C, но у меня есть "реальный мир", когда мне нужно работать с такими типами, как выше.
Ответы
Ответ 1
Правило С++ для типа возврата и значения new T
:
- Если
T
не является типом массива, возвращаемый тип T *
, а возвращаемое значение является указателем на динамически выделенный объект типа T
.
- Если
T
является массивом типа U
, возвращаемый тип U *
, а возвращаемое значение является указателем на первый элемент (тип которого U
) динамически выделенного массива типа T
.
Следовательно, поскольку ваш H
является массивом int
, возвращаемым типом new H
является int *
, а не H *
.
По тому же правилу, new H[1]
возвращает H *
, но обратите внимание, что у вас есть технически выделенный двумерный массив int
s размером 1 x 4.
Лучший способ обойти это в общем коде - использовать auto
:
auto h = new H;
Или, если вы предпочитаете выделить факт указателя:
auto *h = new H;
Что касается обоснования этой кажущейся несогласованности в правилах: указатели на массивы довольно "опасны" на С++, так как они ведут себя довольно неожиданно (т.е. вы должны быть очень осторожны с ними, чтобы не создавать нежелательные эффекты). Посмотрите на этот код:
typedef int H[4];
H *h = obtain_pointer_to_H_somehow();
h[2] = h[1] + 6;
Вначале (и, возможно, даже втором) взгляд, код, как представляется, добавляет 6 ко второму int
в массив и сохраняет его в третьем int
. Но это не то, что он делает.
Как и для int *p
, p[1]
является int
(по адресу sizeof(int)
смещение байтов от p
), поэтому для H *h
, h[1]
является H
, на адрес 4 * sizeof(int)
смещение байтов от H
. Таким образом, код интерпретируется как: берет адрес в H
, добавляет к нему 4 * sizeof(int)
байты, затем добавляет 6, а затем сохраняет результирующий адрес со смещением 8 * sizeof(int)
от H
. Конечно, это не удастся, так как h[2]
распадается на r-значение.
ОК, тогда вы исправляете это следующим образом:
*h[2] = *h[1] + 6;
Сейчас хуже. []
связывается более жестко, чем *
, поэтому он достигнет 5-го объекта int
после H
(обратите внимание, что там всего 4 из них!), добавьте 6 и напишите это в 9-й int
после H
. Запись в случайную память FTW.
Чтобы на самом деле сделать то, что, вероятно, был предназначен для кода, должно быть написано следующим образом:
(*h)[2] = (*h)[1] + 6;
В свете вышесказанного, и поскольку то, что вы обычно делаете с динамически распределенным массивом, является доступ к его элементам, имеет смысл возвращать new T[]
T *
.
Ответ 2
Основной вопрос
[Что такое] Предпочтительный метод для new
указателя на массив
Ограничение:
Заметьте также, что я знаю, что использование std::vector, std:: array и др. обычно предпочтительнее в массивах C-стиля, но у меня есть "реальный мир", где мне нужно работать с типами как указано выше.
Ответ:
для случая без массива:
#include <memory>
auto h = std::make_unique<H>();
// h is now a std::unique_ptr<H> which behaves to all intents and purposes
// like an H* but will safely release resources when it goes out of
// scope
// it is pointing to a default-constructed H
// access the underlying object like this:
h.get(); // yields H*
*h; // yields H&
Для случая массива:
#include <memory>
auto h = std::make_unique<H[]>(4);
// h is now a std::unique_ptr<H[]> which behaves to all intents and purposes
// like an H* but will safely destruct and deallocate the array
// when it goes out of scope
// it is pointing to an array of 4 default-constructed Hs
// access the underlying object like this:
h[1]; // yields H& - a reference to the 2nd H
h.get(); //yields H* - as if &h[0]
Ответ 3
В стандарте С++ new
и new[]
специально разделены, поскольку они могут быть разными распределителями для повышения эффективности и эффективности; выделение массива по сравнению с одним объектом имеет разные шаблоны использования, оптимизирующие реализации распределителей.
Синтаксис, вероятно, отличается от того, что методы интроспекции типа времени компиляции, доступные при стандартизации, были не так надежны, как сегодня.
Чтобы сделать разумный код, предпочтительный способ ИМО:
struct H { int values[4]; }
H * h = new H;
Таким образом, ваш H
тип логически "содержит" ваш массив из четырех значений int
, но структура в памяти должна быть совместима (assert(sizeof(H) == 4 * sizeof(int))
); и вы получаете возможность использовать объектное размещение ваших массивов фиксированного размера. Подробнее... С++ - ey.
Ответ 4
Вы можете понять, почему, если вы в основном выполняете подстановку typedef вручную:
H * h = new H;
Уменьшает до:
int[4]* h = new int[4];
а
H * h = new H[1];
сводится к:
(int*)[4] h = new int[1][4];
Из-за правил обращения к указателю на указатель, это законно.
Если вы хотите использовать обходной путь, и знаете тип H, вы можете сделать что-то вроде:
typedef int* J;
typedef int H[4];
J j = new H; // This will work