new выражение
Создаёт и инициализирует объекты с динамической длительностью хранения, то есть объекты, время жизни которых не обязательно ограничено областью видимости, в которой они были созданы.
Синтаксис
::(необязательно) new (type ) new-initializer (необязательно) | (1) | |
::(необязательно) new type new-initializer (необязательно) | (2) | |
::(необязательно) new (placement-args ) (type ) new-initializer (необязательно) | (3) | |
::(необязательно) new (placement-args ) type new-initializer (необязательно) | (4) |
Пояснение
| type | - | целевой идентификатор типа |
| new-initializer | - | список выражений в круглых скобках или список инициализаторов в фигурных скобках(начиная с C++11) |
| placement-args | - | дополнительные аргументы размещения |
Выражение new пытается выделить память, а затем создать и инициализировать в выделенной памяти либо один безымянный объект, либо безымянный массив объектов. Выражение new возвращает prvalue-указатель на созданный объект или, если был создан массив объектов, указатель на первый элемент массива.
Синтаксис (1) или (3) требуется, если type содержит круглые скобки:
new int(*[10])(); // error: parsed as (new int) (*[10]) () new (int (*[10])()); // okay: allocates an array of 10 pointers to functions
Кроме того, type разбирается жадно: в него включается каждый токен, который может быть частью декларатора:
new int + 1; // okay: parsed as (new int) + 1, increments a pointer returned by new int new int * 1; // error: parsed as (new int*) (1)
new-initializer обязателен, если
- type — это массив неизвестной границы,
| (начиная с C++11) |
| (начиная с C++17) |
double* p = new double[]{1, 2, 3}; // creates an array of type double[3]
auto p = new auto('c'); // creates a single object of type char. p is a char*
auto q = new std::integral auto(1); // OK: q is an int*
auto q = new std::floating_point auto(true) // ERROR: type constraint not satisfied
auto r = new std::pair(1, true); // OK: r is a std::pair<int, bool>*
auto r = new std::vector; // ERROR: element type can't be deduced
Динамические массивы
Если type является типом массива, все измерения, кроме первого, должны быть указаны как положительные целочисленные константные выражения(до C++14)преобразованные константные выражения типа std::size_t(начиная с C++14), однако (только при использовании синтаксиса без круглых скобок (2) и (4)) первое измерение может быть выражением целочисленного типа, перечислимого типа или типа класса с единственной неявной функцией преобразования в целочисленный или перечислимый тип(до C++14)любым выражением, преобразуемым в std::size_t(начиная с C++14). Это единственный способ напрямую создать массив с размером, определяемым во время выполнения; такие массивы часто называют динамическими массивами:
int n = 42; double a[n][5]; // error auto p1 = new double[n][5]; // OK auto p2 = new double[5][n]; // error: only the first dimension may be non-constant auto p3 = new (double[n][5]); // error: syntax (1) cannot be used for dynamic arrays
| Поведение не определено, если значение первого измерения (при необходимости преобразованное в целочисленный или перечислимый тип) отрицательно. | (до C++11) |
| В следующих случаях значение выражения, задающего первое измерение, является недопустимым:
Если значение первого измерения недопустимо по любой из этих причин,
| (начиная с C++11) |
Нулевое значение первого измерения допустимо, и функция выделения памяти вызывается.
| Если new-initializer является списком инициализаторов в фигурных скобках, а первое измерение потенциально вычисляется и не является основным константным выражением, проверяются семантические ограничения копирующей инициализации гипотетического элемента массива из пустого списка инициализаторов. | (начиная с C++11) |
Выделение памяти
Выражение new выделяет память, вызывая соответствующую функцию выделения памяти. Если type не является типом массива, имя функции — operator new. Если type является типом массива, имя функции — operator new[].
Как описано в разделе функция выделения памяти, программа на C++ может предоставлять глобальные и специфичные для класса замены этих функций. Если выражение new начинается с необязательного оператора ::, например ::new T или ::new T[n], специфичные для класса замены игнорируются (поиск функции выполняется в глобальной области видимости). В противном случае, если T является типом класса, поиск начинается в области видимости класса T.
При вызове функции выделения памяти выражение new передаёт в качестве первого аргумента запрошенное число байтов типа std::size_t, которое для не-массива T в точности равно sizeof(T).
При выделении памяти для массива могут предоставляться дополнительные накладные расходы, размер которых может различаться от одного вызова new к другому, если только выбранная функция выделения памяти не является стандартной функцией, не выделяющей память. Указатель, возвращаемый выражением new, будет смещён на это значение относительно указателя, возвращённого функцией выделения памяти. Многие реализации используют накладные расходы массива для хранения числа объектов в массиве, которое выражение delete[] использует для вызова нужного числа деструкторов. Кроме того, если выражение new используется для выделения массива char, unsignedchar или std::byte(начиная с C++17), оно может запросить у функции выделения памяти дополнительную память, если это необходимо для гарантии правильного выравнивания объектов всех типов, размер которых не превышает запрошенный размер массива, если впоследствии такой объект будет помещён в выделенный массив.
| Выражения new могут устранять или объединять выделения памяти, выполняемые с помощью заменяемых функций выделения памяти. При устранении память может быть предоставлена компилятором без вызова функции выделения памяти (это также позволяет оптимизировать неиспользуемое выражение new). При объединении выделение памяти, выполняемое выражением new E1, строго включает время жизни объекта, выделенного выражением E2. 2) E1 и E2 вызвали бы одну и ту же заменяемую глобальную функцию выделения памяти. 3) Для функции выделения памяти, выбрасывающей исключения, исключения в E1 и E2 были бы впервые перехвачены одним и тем же обработчиком. Обратите внимание, что эта оптимизация разрешена только при использовании выражений new, но не при использовании других способов вызова заменяемой функции выделения памяти: | (начиная с C++14) |
| При вычислении константного выражения вызов функции выделения памяти всегда опускается. В константных выражениях можно вычислять только те выражения new, которые в противном случае вызвали бы заменяемую глобальную функцию выделения памяти. | (начиная с C++20) |
new с размещением
Если указаны placement-args, они передаются функции выделения памяти как дополнительные аргументы. Такие функции выделения памяти известны как «new с размещением» — по названию стандартной функции выделения памяти void* operator new(std::size_t, void*), которая просто возвращает свой второй аргумент без изменений. Этот механизм используется для создания объектов в выделенной памяти:
// within any block scope...
{
// Statically allocate the storage with automatic storage duration
// which is large enough for any object of type “T”.
alignas(T) unsigned char buf[sizeof(T)];
T* tptr = new(buf) T; // Construct a “T” object, placing it directly into your
// pre-allocated storage at memory address “buf”.
tptr->~T(); // You must **manually** call the object's destructor
// if its side effects is depended by the program.
} // Leaving this block scope automatically deallocates “buf”.
Примечание: эта функциональность инкапсулирована в функциях-членах классов Allocator.
| При выделении объекта, требование к выравниванию которого превышает | (начиная с C++17) |
new T; // calls operator new(sizeof(T))
// (C++17) or operator new(sizeof(T), std::align_val_t(alignof(T))))
new T[5]; // calls operator new[](sizeof(T)*5 + overhead)
// (C++17) or operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T))))
new(2,f) T; // calls operator new(sizeof(T), 2, f)
// (C++17) or operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)
Если функция выделения памяти, не выбрасывающая исключений (например, выбранная с помощью new(std::nothrow) T), возвращает нулевой указатель из-за ошибки выделения памяти, выражение new немедленно возвращает его и не пытается инициализировать объект или вызвать функцию освобождения памяти. Если нулевой указатель передан в качестве аргумента выражению new с размещением, не выделяющему память, в результате чего выбранная стандартная функция размещения, не выделяющая память, возвращает нулевой указатель, поведение не определено.
Инициализация
Объект, созданный выражением new, инициализируется по следующим правилам.
Если type не является типом массива, единственный объект создаётся в полученной области памяти:
- Если new-initializer отсутствует, объект инициализируется по умолчанию.
- Если new-initializer является списком выражений в круглых скобках, объект инициализируется напрямую.
| (начиная с C++11) |
Если type является типом массива, инициализируется массив объектов:
- Если new-initializer отсутствует, каждый элемент инициализируется по умолчанию.
- Даже если первое измерение равно нулю, семантические ограничения инициализации по умолчанию гипотетического элемента всё равно должны выполняться.
- Если new-initializer — пара круглых скобок, каждый элемент инициализируется значением.
- Даже если первое измерение равно нулю, семантические ограничения инициализации значением гипотетического элемента всё равно должны выполняться.
| (начиная с C++11) |
| (начиная с C++20) |
Ошибка инициализации
Если инициализация завершается выбрасыванием исключения (например, из конструктора), программа ищет соответствующую функцию освобождения памяти, после чего:
- Если подходящая функция освобождения памяти найдена, она вызывается для освобождения памяти, в которой создавался объект. После этого исключение продолжает распространяться в контексте выражения new.
- Если однозначно соответствующую функцию освобождения памяти найти не удаётся, распространение исключения не приводит к освобождению памяти объекта. Это допустимо только в том случае, если вызванная функция выделения памяти не выделяет память; в противном случае это, вероятно, приведёт к утечке памяти.
Область поиска соответствующей функции освобождения памяти определяется следующим образом:
- Если выражение new не начинается с
::, а выделяемый тип является типом классаTили массивом типа классаT, имя функции освобождения памяти ищется в области видимости классаT. - В противном случае, а также если в области видимости класса
Tничего не найдено, имя функции освобождения памяти ищется в глобальной области видимости.
Для функции выделения памяти без размещения используется обычный поиск соответствующей функции освобождения памяти (см. выражение delete).
Для функции выделения памяти с размещением соответствующая функция освобождения памяти должна иметь такое же число параметров, а тип каждого параметра, кроме первого, должен совпадать с типом соответствующего параметра функции выделения памяти (после преобразований параметров).
- Если поиск находит одну соответствующую функцию освобождения памяти, она будет вызвана; в противном случае функция освобождения памяти вызвана не будет.
- Если поиск находит функцию освобождения памяти без размещения и эта функция, рассматриваемая как функция освобождения памяти с размещением, была бы выбрана в качестве соответствия функции выделения памяти, программа некорректна.
В любом случае соответствующая функция освобождения памяти (если она есть) должна быть не удалённой и(начиная с C++11) доступной в точке появления выражения new.
struct S
{
// Placement allocation function:
static void* operator new(std::size_t, std::size_t);
// Non-placement deallocation function:
static void operator delete(void*, std::size_t);
};
S* p = new (0) S; // error: non-placement deallocation function matches
// placement allocation function
Если функция освобождения памяти вызывается в выражении new (из-за ошибки инициализации), передаваемые ей аргументы определяются следующим образом:
- Первый аргумент — значение (типа void*), возвращённое вызовом функции выделения памяти.
- Остальные аргументы (только для функций освобождения памяти с размещением) — это placement-args, переданные функции выделения памяти с размещением.
Если реализации разрешено создать временный объект или сделать копию любого аргумента при вызове функции выделения памяти, не определено, используется ли один и тот же объект при вызове функций выделения и освобождения памяти.
Утечки памяти
Объекты, созданные выражениями new (объекты с динамической длительностью хранения), существуют до тех пор, пока указатель, возвращённый выражением new, не будет использован в соответствующем выражении delete. Если исходное значение указателя утрачено, объект становится недоступным, и освободить его память невозможно: возникает утечка памяти.
Это может произойти, если указатель присваивается:
int* p = new int(7); // dynamically allocated int with value 7 p = nullptr; // memory leak
или если указатель выходит за пределы области видимости:
void f()
{
int* p = new int(7);
} // memory leak
или вследствие исключения:
void f()
{
int* p = new int(7);
g(); // may throw
delete p; // okay if no exception
} // memory leak if g() throws
Чтобы упростить управление динамически выделяемыми объектами, результат выражения new часто сохраняется в умном указателе: std::auto_ptr (до C++17)std::unique_ptr или std::shared_ptr(начиная с C++11). Эти указатели гарантируют выполнение выражения delete в описанных выше ситуациях.
Примечания
ABI Itanium C++ требует, чтобы накладные расходы при выделении памяти для массива были равны нулю, если тип элемента созданного массива допускает тривиальное разрушение. То же требование действует и в MSVC.
Некоторые реализации (например, MSVC до VS 2019 v16.7) требуют ненулевых накладных расходов при выделении массива с помощью размещения new, не выделяющего память, если тип элемента не допускает тривиальное разрушение. Это больше не соответствует стандарту после решения CWG 2382.
Выражение размещения new, не выделяющее память, которое создаёт массив unsignedchar или std::byte(начиная с C++17), можно использовать для неявного создания объектов в заданной области памяти: оно завершает время жизни объектов, перекрывающихся с массивом, а затем неявно создаёт в массиве объекты типов с неявным временем жизни.
std::vector предоставляет похожие возможности для одномерных динамических массивов.
| Макрос проверки поддержки функции | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_constexpr | 202406L | (C++26) | Размещение new в constexpr-контексте |
Ключевые слова
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение, описанное в опубликованном стандарте | Исправленное поведение |
|---|---|---|---|
| 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]могло выбросить std::bad_array_new_length | изменено на возврат нулевого указателя |
| CWG 2102 | C++98 | было неясно, должна ли инициализация по умолчанию/значением быть корректной при инициализации пустых массивов | должна быть корректной |
| CWG 2382 | C++98 | размещение массива с помощью new, не выделяющее память, могло требовать накладных расходов при выделении | такие накладные расходы запрещены |
| CWG 2392 | C++11 | программа могла быть некорректной, даже если первое измерение не вычислялось потенциально | в этом случае программа корректна |
| P1009R2 | C++11 | границу массива нельзя было вывести в выражении new | выведение разрешено |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/new