Выражение new
Создаёт и инициализирует объекты с динамической длительностью хранения, то есть объекты, время жизни которых не обязательно ограничено областью видимости, в которой они были созданы.
Содержание |
[править]Синтаксис
:: (необязательно)new ( тип) инициализатор (необязательно) | (1) | ||||||||
:: (необязательно)new new-типинициализатор (необязательно) | (2) | ||||||||
:: (необязательно)new ( параметры-размещения) ( тип) инициализатор (необязательно) | (3) | ||||||||
:: (необязательно)new ( параметры-размещения) new-типинициализатор (необязательно) | (4) | ||||||||
new int(*[10])();// ошибка: анализируется как (new int) (*[10]) () new (int(*[10])());// правильно: выделяет массив из 10 указателей на функции
- Вдобавок new-тип жадный: он будет включать каждый токен, который может быть частью декларатора:
new int+1;// правильно: анализируется как (new int) + 1,// инкрементирует указатель, возвращаемый new int new int*1;// ошибка: анализируется как (new int*) (1)
инициализатор не является обязательным, если
- тип или new-тип это массив с неизвестной границей
| (начиная с C++11) |
| (начиная с C++17) |
double* p = new double[]{1,2,3};// создаёт массив типа double[3]auto p = new auto('c');// создает единственный объект типа char. p это char* auto q = new std::integralauto(1);// OK: q это int*auto q = new std::floating_pointauto(true)// ОШИБКА: ограничение типа не выполнено auto r = new std::pair(1, true);// OK: r это std::pair<int, bool>*auto r = new std::vector;// ОШИБКА: невозможно вывести тип элемента
[править]Объяснение
Выражение new
пытается выделить память, а затем пытается создать и инициализировать либо один безымянный объект, либо безымянный массив объектов в выделенной памяти. Выражение new возвращает указатель prvalue на сконструированный объект или, если был построен массив объектов, указатель на начальный элемент массива.
Если тип
является типом массива, все измерения, кроме первого, должны быть указаны как положительные интегральные константные выражения(до C++14)преобразованное константное выражение типа std::size_t(начиная с C++14), но (только при использовании синтаксиса без скобок (2) и (4)) первое измерение может быть выражением целочисленного типа, типа перечисления или тип класса с единственной неявной функцией преобразования в целочисленный или перечисляемый тип(до C++14)любым выражением, конвертируемым в std::size_t(начиная с C++14). Это единственный способ напрямую создать массив с размером, определённым во время выполнения, такие массивы часто называют динамическими массивами:
int n =42;double a[n][5];// ошибкаauto p1 = new double[n][5];// OKauto p2 = new double[5][n];// ошибка: только первое измерение может быть// неконстантнымauto p3 = new (double[n][5]);// ошибка: синтаксис (1) нельзя использовать// для динамических массивов
Поведение не определено, если значение в первом измерении (преобразованном в целочисленный или перечисляемый тип, если необходимо) отрицательное. | (до C++11) |
В следующих случаях выражение, определяющее первое измерение, ошибочно:
Если значение в первом измерении ошибочно по любой из этих причин,
| (начиная с C++11) |
Приемлемо первое измерение, равное нулю, и вызывается функция распределения.
Примечание: std::vector предлагает аналогичные функции для одномерных динамических массивов.
[править]Распределение
Выражение new выделяет память, вызывая соответствующую функцию распределения. Если тип
не является типом массива, имя функции operator new
. Если тип
является типом массива, имя функции operator new[]
.
Как описано в функция распределения, программа на C++ может предоставить глобальную замену и замену для конкретных классов этих функций. Если выражение new начинается с необязательного оператора ::, как в ::new T или ::new T[n], замены для конкретных классов будут проигнорированы (функция ищется в глобальной области видимости). В противном случае, если T
является типом класса, поиск начинается в области видимости класса T
.
При вызове функции распределения выражение new передаёт количество запрошенных байтов в качестве первого аргумента типа std::size_t, что в точности равно sizeof(T) для не массива T
.
Выделение массива может привести к неопределённым накладным расходам, которые могут варьироваться от одного вызова к другому, если только выбранная функция распределения не является стандартной формой без выделения. Указатель, возвращаемый выражением new будет смещён на это значение от указателя, возвращаемого функцией распределения. Многие реализации используют служебные данные массива для хранения количества объектов в массиве, который используется выражением delete[] для вызова правильного количества деструкторов. Кроме того, если выражение new используется для выделения массива из char, unsignedchar, или std::byte(начиная с C++17), оно может запросить дополнительную память у функции распределения, если необходимо гарантировать правильное выравнивание объектов всех типов, не превышающих размер запрошенного массива, если один из них позже будет помещён в выделенный массив.
Выражения new могут исключать или комбинировать выделения, сделанные с помощью заменяемых функций выделения. В случае исключения, хранилище может быть предоставлено компилятором без вызова функции распределения (это также позволяет оптимизировать неиспользуемое выражение new). В случае объединения выделение, сделанное выражением new E1, может быть расширено, чтобы обеспечить дополнительное хранилище для другого выражения new E2, если все следующее верно: 1) Время жизни объекта, выделенного E1, строго содержит время жизни объекта, выделенного E2, 2) E1 и E2 будут вызывать одну и ту же заменяемую глобальную функцию распределения 3) Для бросающей исключение функции распределения исключения в E1 и E2 сначала будут перехвачены одним и тем же обработчиком. Обратите внимание, что эта оптимизация разрешена только тогда, когда используются выражения new, а не какие-либо другие методы вызова заменяемой функции выделения: delete[] new int[10]; Обратите внимание, что эта оптимизация разрешена только тогда, когда используются выражения new, а не какие-либо другие методы для вызова заменяемой функции выделения: delete [] new int[10]; можно оптимизировать, но operator delete(operator new(10)); нет. | (начиная с C++14) |
Во время оценки константного выражения, вызов функции распределения всегда опускается. Только выражения new, которые в противном случае привели бы к вызову заменяемой глобальной функции распределения, могут быть вычислены в константных выражениях. | (начиная с C++20) |
[править]Размещающий new
Если предоставлены параметры-размещения
, они передаются в функцию распределения в качестве дополнительных аргументов. Такие функции распределения известны как "размещающий new", после стандартной функции распределения void*operator new(std::size_t, void*), которая просто возвращает свой второй аргумент без изменений. Это используется для создания объектов в выделенной памяти:
// в пределах любого блока видимости...{// Статически выделяет память с автоматической длительностью хранения,// достаточной для любого объекта типа `T`. alignas(T)unsignedchar buf[sizeof(T)]; T* tptr = new(buf) T;// Создаёт объект `T`, помещая его прямо в заранее // выделенное хранилище по адресу памяти `buf`. tptr->~T();// Вы должны **вручную** вызвать деструктор объекта,// если его побочные эффекты зависят от программы.}// Выход из этой области видимости блока автоматически // освобождает `buf`.
Примечание: эта функциональность инкапсулирована функциями-элементами классов Allocator.
При выделении объекта, требование выравнивания которого превышает | (начиная с C++17) |
new T;// вызывает operator new(sizeof(T))// (C++17) или operator new(sizeof(T), std::align_val_t(alignof(T)))) new T[5];// вызывает operator new[](sizeof(T)*5 + overhead)// (C++17) или operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T)))) new(2,f) T;// вызывает operator new(sizeof(T), 2, f)// (C++17) или operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)
Если функция выделения, не бросающая исключения, (например, выбранная с помощью new(std::nothrow) T) возвращает нулевой указатель из-за сбоя выделения, тогда выражение new возвращается немедленно, оно не пытается инициализировать объект или вызвать функцию освобождения. Если нулевой указатель передаётся в качестве аргумента не выделяющему память выражению new, что заставляет выбранную стандартную функцию выделения памяти без выделения памяти возвращать нулевой указатель, поведение не определено.
[править]Конструирование
Объект, созданный выражением new, инициализируется в соответствии со следующими правилами:
- Для
типа
не-массива, в полученной области памяти создаётся единственный объект.
- Если инициализатор отсутствует, объект инициализируются по умолчанию.
- Если инициализатор это список аргументов в скобках, объект инициализируется напрямую.
| (начиная с C++11) |
- Если тип или new-тип является типом массива, инициализируется массив объектов.
- Если инициализатор отсутствует, каждый элемент инициализируется по умолчанию
- Если инициализатор это пустая пара круглых скобок, каждый элемент инициализируется значением.
| (начиная с C++11) |
| (начиная с C++20) |
Если инициализация завершается выдачей исключения (например, из конструктора), если выражение new выделило какое-либо хранилище, оно вызывает соответствующую функцию освобождения: operator delete для типа
не массива, operator delete[] для типа
массива. Функция освобождения ищется в глобальной области видимости, если выражение new использовало синтаксис ::new, в противном случае она ищется в области видимости T
, если T
это тип класса. Если неудачная функция распределения была обычной (без размещения), поиск функции освобождения выполняется согласно правилам, описанным в выражении delete. Для неудачного размещающего new, все типы параметров, кроме первого, соответствующей функции освобождения должны быть идентичны параметрам размещающего new. При вызове функции освобождения памяти используется значение, полученное ранее из функции распределения, переданное в качестве первого аргумента, выравнивание передаётся как необязательный аргумент выравнивания(начиная с C++17), и параметры-размещения
, если есть, передаются в качестве дополнительных аргументов размещения. Если функция освобождения не найдена, память не освобождается.
[править]Утечки памяти
Объекты, созданные выражениями new (объекты с динамической длительностью хранения), сохраняются до тех пор, пока указатель, возвращаемый выражением new, не будет использован в выражении delete. Если исходное значение указателя потеряно, объект становится недоступным и не может быть освобожден: происходит утечка памяти.
Это может произойти, если указатель назначен на:
int* p = new int(7);// динамически выделяемый int со значением 7 p = nullptr;// утечка памяти
или если указатель выходит за пределы области видимости:
void f(){int* p = new int(7);}// утечка памяти
или в связи с исключением:
void f(){int* p = new int(7); g();// может бросить исключение delete p;// хорошо, если нет исключения}// утечка памяти, если g() бросает исключение
Чтобы упростить управление динамически выделяемыми объектами, результат выражения new часто сохраняется в умном указателе: std::auto_ptr(до C++17)std::unique_ptr или std::shared_ptr(начиная с C++11). Эти указатели гарантируют, что выражение delete выполняется в ситуациях, показанных выше.
[править]Ключевые слова
[править]Примечание
Itanium C++ ABI требует, чтобы накладные расходы на выделение массива равнялись нулю, если тип элемента созданного массива тривиально разрушаем. То же самое и с MSVC.
Некоторые реализации (например, MSVC до VS 2019 v16.7) требуют накладные расходы на ненулевое выделение массива для размещения массива без выделения оператором new, если тип элемента не является тривиально разрушаемым, что больше не соответствует, поскольку CWG 2382.
Выражение new размещения массива без выделения памяти, которое создаёт массив из unsignedchar или std::byte(начиная с C++17) можно использовать для неявного создания объектов в заданной области хранения: это завершает время существования объектов, перекрывающихся с массивом, а затем неявно создаёт в массиве объекты с типами неявного времени жизни.
[править]Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 74 | C++98 | значение в первом измерении должно иметь целочисленный тип | разрешены типы перечисления |
CWG 299 | C++98 | значение в первом измерении должно иметь целочисленный или перечисляемый тип | разрешены типы классов с единственной функцией преобразования в целочисленный или перечисляемый тип |
CWG 624 | C++98 | поведение было неопределённым, когда размер выделенного объекта превышал предел, определённый реализацией | память не выделяется, и в этом случае выдаётся исключение |
CWG 1748 | C++98 | размещающему new без выделения памяти необходимо проверить, является ли аргумент нулевым | неопределённое поведение для нулевого аргумента |
CWG 1992 | C++11 | new (std::nothrow)int[N] может выброситьbad_array_new_length | изменено на возврат нулевого указателя |
WG не указан | C++11 | граница массива не может быть выведена в выражении new | вывод разрешён |
CWG 2382 | C++98 | размещение массива оператором new без выделения памяти может потребовать дополнительных затрат на выделение ресурсов | такие накладные расходы на распределение запрещены |
CWG 2392 | C++11 | программа может быть некорректна, даже если первое измерение потенциально не оценивается | в этом случае некорректна |
WG не указан | C++11 | граница массива не может быть выведена в выражении new | вывод разрешён |
WG не указан | C++98 | предыдущая объектная модель не поддерживала многие полезные идиомы, требуемые стандартной библиотекой, и не была совместима с эффективными типами в C | добавлено неявное создание объекта |
CWG 2489 | C++98 | char[] не может предоставить хранилище, но объекты могут быть неявно созданы в его хранилище | объекты не могут быть созданы неявно в хранилище char[] |