Spec-Zone.ru › C++

Оператор new

Создаёт и инициализирует объекты с динамическим сроком жизни, то есть объекты, чья продолжительность существования не обязательно ограничена областью видимости, в которой они были созданы.

Синтаксис

::(необязательно) new (тип ) инициализатор (необязательно) (1)
::(необязательно) new тип инициализатор (необязательно) (2)
::(необязательно) new (аргументы_размещения ) (тип ) инициализатор (необязательно) (3)
::(необязательно) new (аргументы_размещения ) тип инициализатор (необязательно) (4)
1,2) Попытка создать объект типа, обозначенного идентификатором типа тип, который может быть типом массива, и может включать спецификатор типа-заполнитель auto(с C++11), или содержать имя шаблонного класса, аргументы которого должны быть выведены с помощью вывода аргументов шаблона класса(с C++17).
3,4) То же самое, что и (1,2), но предоставляет дополнительные аргументы функции выделения памяти, см. placement new.

Объяснение

тип - целевой идентификатор типа
инициализатор - список выражений в скобках или инициализатор_с_фигурными_скобками(с C++11)
аргументы_размещения - дополнительные аргументы размещения


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

Синтаксис (1) или (3) требуется, если тип включает скобки:

new int(*[10])();    // error: parsed as (new int) (*[10]) ()
new (int (*[10])()); // okay: allocates an array of 10 pointers to functions

Кроме того, тип анализируется жадно: он будет включать каждый токен, который может быть частью декларатора:

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)

Инициализатор инициализатор не является необязательным, если

  • тип является массивом неизвестной размерности,
  • используется заполнитель в тип, то есть auto или decltype(auto)(с C++14), возможно, в сочетании с ограничением типа(с C++20),
(с 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

Динамические массивы

Если тип является типом массива, все измерения, кроме первого, должны быть указаны как положительные целые константные выражения(до 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)

В следующих случаях выражение, определяющее первое измерение, является ошибочным:

  • выражение имеет нетиповой класс и его значение до преобразования в std::size_t отрицательно;
  • выражение имеет тип класса, и его значение после функции пользовательского преобразования и перед вторым стандартным преобразованием отрицательно;
  • значение выражения больше, чем какое-то определенное реализацией ограничение;
  • значение меньше, чем количество элементов массива, указанных в инициализаторе в фигурных скобках (включая завершающее '\0' в строковой литерале).

Если значение в первом измерении является ошибочным по какой-либо из этих причин,

  • если после преобразования в std::size_t, первое измерение является ядерным константным выражением и оно возможно вычисляется, программа является ошибочной,
  • в противном случае, если функция выделения памяти, которая должна была быть вызвана, не вызывает исключения (включая перегрузки std::nothrow которые не объявлены noexcept), оператор new не вызывает функцию выделения, а вместо этого генерирует исключение типа, соответствующего обработчику исключений типа std::bad_array_new_length,
  • в противном случае оператор new возвращает нулевой указатель требуемого типа результата.
(с C++11)

Первое измерение нуля допустимо, и функция выделения памяти вызывается.

Если инициализатор является инициализатор_с_фигурными_скобками, и первое измерение возможно вычисляется и не является ядерным константным выражением, проверяются семантические ограничения копирующей инициализации гипотетического элемента массива из пустого списка инициализаторов.

(с C++11)

Выделение памяти

Оператор new выделяет память, вызывая соответствующую функцию выделения. Если тип является типом без массива, имя функции — operator new. Если тип является типом массива, имя функции — operator new[].

Как описано в функции выделения, программа C++ может предоставить глобальные и специфичные для класса замены для этих функций. Если оператор new начинается с необязательного :: оператора, как в ::new T или ::new T[n], специфичные для класса замены будут проигнорированы (функция ищется в глобальной области видимости). В противном случае, если T является типом класса, поиск начинается в области видимости класса T.

При вызове функции выделения оператор new передает количество затребованных байтов в качестве первого аргумента типа std::size_t, которое равно точно sizeof(T) для типов без массива T.

Выделение памяти массива может использовать неопределённые накладные расходы, которые могут варьироваться от одного вызова new к другому, если только выбранная функция выделения не является стандартной, не требующей выделения. Указатель, возвращаемый выражением new, будет смещён на это значение относительно указателя, возвращённого функцией выделения. Многие реализации используют накладные расходы массива для хранения количества объектов в массиве, которое используется выражением delete[] для вызова соответствующего количества деструкторов. Кроме того, если выражение new используется для выделения массива char, unsigned char, или std::byte(с C++17), может потребоваться дополнительная память от функции выделения, если необходимо, чтобы гарантировать правильное выравнивание объектов всех типов не больше, чем запрошенный размер массива, если позже один из них будет помещён в выделенный массив.

Выражения new разрешается пропускать или объединять выделения, выполненные через заменяемые функции выделения. В случае пропускания, хранилище может быть предоставлено компилятором без вызова функции выделения (это также позволяет оптимизировать неиспользуемые выражения new). В случае объединения, выделение, выполненное выражением new E1 может быть расширено для предоставления дополнительного хранилища для другого выражения new E2 если выполняются все следующие условия:

1) Жизненный цикл объекта, выделенного E1, строго содержит жизненный цикл объекта, выделенного E2. 2) E1 и E2 вызовут одну и ту же заменяемую глобальную функцию выделения. 3) Для функции выделения, выбрасывающей исключения, исключения в E1 и E2 будут сначала перехвачены в одном и том же обработчике.

Обратите внимание, что эта оптимизация разрешена только при использовании выражений new, а не любых других методов для вызова заменяемой функции выделения: delete[] new int[10]; может быть оптимизирована, но operator delete(operator new(10)); нет.

(с 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.

При выделении объекта, требование к выравниванию которого превышает __STDCPP_DEFAULT_NEW_ALIGNMENT__ или массива таких объектов, выражение new передаёт требование к выравниванию (охваченное std::align_val_t) в качестве второго аргумента для функции выделения (для размещающих форм placement-arg появляются после выравнивания, как третий, четвёртый и т. д. аргументы). Если разрешение перегрузки терпит неудачу (что происходит, когда определена функция выделения класса со другой сигнатурой, так как она скрывает глобальные функции), разрешение перегрузки предпринимается второй раз, без выравнивания в списке аргументов. Это позволяет функциям выделения класса, не учитывающим выравнивание, иметь приоритет перед глобальными функциями выделения, учитывающими выравнивание.

(с 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 представляет собой список выражений в скобках, объект инициализируется непосредственно.
  • Если new-initializer является braced-init-list, объект инициализируется списком.
(с C++11)

Если type является типом массива, инициализируется массив объектов:

  • Если new-initializer отсутствует, каждый элемент инициализируется по умолчанию.
    • Даже если первая размерность равна нулю, семантические ограничения инициализации по умолчанию гипотетического элемента всё равно должны быть соблюдены.
  • Если new-initializer представляет собой пару скобок, каждый элемент инициализируется значением по умолчанию.
    • Даже если первая размерность равна нулю, семантические ограничения инициализации значением по умолчанию гипотетического элемента всё равно должны быть соблюдены.
  • Если new-initializer является braced-init-list, массив инициализируется по правилам агрегированной инициализации.
(с C++11)
  • Если new-initializer представляет собой список выражений в скобках, массив инициализируется по правилам агрегированной инициализации.
(с 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 выполняется в ситуациях, показанных выше.

Ключевые слова

new

Примечания

Itanium C++ ABI требует, чтобы накладные расходы на выделение массива были равны нулю, если тип элементов создаваемого массива тривиально разрушаем. То же самое относится к MSVC.

Некоторые реализации (например, MSVC до VS 2019 v16.7) требуют ненулевых накладных расходов на выделение массива при использовании non-allocating placement array new, если тип элемента не тривиально разрушаем, что больше не соответствует стандарту с момента задачи CWG 2382.

Выражение non-allocating placement array new, создающее массив unsigned char, или std::byte(с C++17) может использоваться для неявного создания объектов в заданном регионе памяти: оно завершает жизненный цикл объектов, перекрывающихся с массивом, и затем неявно создаёт объекты с неявным жизненным циклом в массиве.

std::vector предлагает аналогичную функциональность для одномерных динамических массивов.

Отчеты о дефектах

Следующие отчеты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применён к Поведение, как опубликовано Правильное поведение
CWG 74 C++98 значение в первой размерности должно иметь целочисленный тип допускаются типы перечислений
CWG 299 C++98 значение в первой размерности должно
иметь целочисленный или перечислительный тип
классы с одной
функцией преобразования к целочисленному
или перечислительному типу допускаются
CWG 624 C++98 поведение было неопределённым, когда размер выделяемого объекта превышал реализационно-определённый предел в этом случае память не выделяется, и генерируется исключение
CWG 1748 C++98 non-allocating placement new нуждался
в проверке, является ли аргумент нулевым
неопределённое поведение для нулевого аргумента
CWG 1992 C++11 new (std::nothrow) int[N]
мог генерировать std::bad_array_new_length
изменено на возврат нулевого указателя
CWG 2102 C++98 не было ясно, требуется ли начальная инициализация по умолчанию/значению при инициализации пустых массивов требуется
CWG 2382 C++98 non-allocating placement array new
мог потребовать накладных расходов на выделение
такие накладные расходы на выделение запрещены
CWG 2392 C++11 программа могла быть некорректной, даже если первая размерность не является потенциально вычисляемой корректна в этом случае
P1009R2 C++11 размер массива не мог быть
выведен в выражении new
вывод разрешён

См. также

  • конструктор
  • исключение копирования
  • конструктор по умолчанию
  • delete
  • деструктор
  • инициализация
    • инициализация агрегатов
    • инициализация по умолчанию
    • прямая инициализация
    • инициализация списком
    • инициализация значением

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/new

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API