В языке программирования C++ new и delete представляют собой пару языковых конструкций , которые выполняют динамическое выделение памяти , создание объекта и уничтожение объекта . [1]
За исключением формы, называемой "placement new" , оператор new обозначает запрос на выделение памяти в куче процесса . Если памяти достаточно, new инициализирует память, вызывая при необходимости конструкторы объектов, и возвращает адрес в недавно выделенную и инициализированную память. [2] [3] Запрос new в простейшей форме выглядит следующим образом:
p = новый T ;
где p — ранее объявленный указатель типа T (или какой-либо другой тип, которому может быть назначен указатель T , например, суперкласс T ) . Конструктор по умолчанию для T , если таковой имеется, вызывается для создания экземпляра T в выделенном буфере памяти.
Если в свободном хранилище недостаточно памяти для объекта типа T , новый запрос указывает на сбой, выдавая исключение типа std::bad_alloc . Это устраняет необходимость явной проверки результата выделения.
Аналогом освобождения памяти для new является delete , который сначала вызывает деструктор (если таковой имеется) для своего аргумента, а затем возвращает память, выделенную new, обратно в свободное хранилище. Каждый вызов new должен быть сопоставлен с вызовом delete ; невыполнение этого требования приводит к утечке памяти . [1]
новый синтаксис имеет несколько вариантов, которые позволяют более точно контролировать распределение памяти и создание объектов. Синтаксис вызова функции используется для вызова конструктора, отличного от конструктора по умолчанию, и передачи ему аргументов, например,
p = новый T ( аргумент );
вызывает конструктор T с одним аргументом вместо конструктора по умолчанию при инициализации вновь выделенного буфера.
Другой вариант выделяет и инициализирует массивы объектов, а не отдельные объекты:
p = новый T [ N ];
При этом запрашивается буфер памяти из свободного хранилища, достаточно большой для хранения непрерывного массива из N объектов типа T , и вызывается конструктор по умолчанию для каждого элемента массива.
Память, выделенная с помощью new[], должна быть освобождена оператором delete[] , а не delete . Использование неподходящей формы приводит к неопределенному поведению . Компиляторы C++ не обязаны генерировать диагностическое сообщение при использовании неправильной формы.
Стандарт C++11 определяет дополнительный синтаксис,
p = новый T [ N ] { инициализатор1 , ..., инициализаторN };
который инициализирует каждый p[ i ] инициализатором i+1 .
Если new не может найти достаточно памяти для обслуживания запроса на выделение, он может сообщить об ошибке тремя различными способами. Во-первых, стандарт ISO C++ позволяет программам регистрировать пользовательскую функцию, называемую new_handler, в среде выполнения C++ ; если это так, то эта функция вызывается всякий раз, когда new сталкивается с ошибкой. New_handler может попытаться сделать больше памяти доступной или завершить программу, если не может.
Если new_handler не установлен, new вместо этого выдает исключение типа std::bad_alloc . Таким образом, программе не нужно проверять значение возвращаемого указателя, как это принято в C ; если исключение не было выдано, выделение прошло успешно.
Третий метод обработки ошибок предоставляется вариантной формой new(std::nothrow) , которая указывает, что не следует выдавать никаких исключений; вместо этого возвращается нулевой указатель , сигнализирующий об ошибке выделения памяти.
Оператор new может быть перегружен, так что определенные типы (классы) используют пользовательские алгоритмы выделения памяти для своих экземпляров. Например, ниже приведен вариант шаблона singleton , где первый новый вызов Singleton выделяет экземпляр, а все последующие вызовы возвращают этот же экземпляр:
#include <cstdlib> #include <cstddef> класс Синглтон { общественность : статический void * оператор новый ( std :: size_t размер ) { если ( ! экземпляр ) { экземпляр = std :: malloc ( размер ); } refcount ++ ; возвратить экземпляр ; } статический оператор void удалить ( void * ) noexcept { если ( -- refcount == 0 ) { std :: free ( экземпляр ); экземпляр = nullptr ; } }частный : статический void * экземпляр = nullptr ; статический std :: size_t refcount = 0 ; };
Эта функция была доступна с самого начала истории C++, хотя конкретный механизм перегрузки изменился. Она была добавлена в язык, поскольку объектно-ориентированные программы C++ имели тенденцию выделять много небольших объектов с помощью new , который внутренне использовал распределитель C (см. § Связь с malloc и free); который, однако, был оптимизирован для меньшего количества и больших выделений, выполняемых типичными программами C. Страуструп сообщил, что в ранних приложениях функция C malloc была «наиболее распространенным узким местом производительности в реальных системах», при этом программы тратили до 50% своего времени на эту функцию. [4]
Поскольку стандарт C++ включает в себя стандартную библиотеку C , процедуры динамического выделения памяти C malloc , calloc , realloc и free также доступны программистам C++. Использование этих процедур не рекомендуется для большинства случаев, поскольку они не выполняют инициализацию и уничтожение объектов. [5] new и delete были, по сути, введены в первой версии C++ (тогда называвшейся « C with Classes »), чтобы избежать необходимости ручной инициализации объектов. [4]
В отличие от процедур C, которые позволяют увеличивать или уменьшать выделенный массив с помощью realloc , невозможно изменить размер буфера памяти, выделенного с помощью new[] . Вместо этого стандартная библиотека C++ предоставляет динамический массив (коллекцию), который можно расширить или уменьшить в своем классе-шаблоне std::vector .
Стандарт C++ не определяет никакой связи между new / delete и процедурами выделения памяти C, но new и delete обычно реализуются как оболочки вокруг malloc и free . [6] Смешение двух семейств операций, например, освобождение только что выделенной памяти или удаление malloc - отведенной памяти, вызывает неопределенное поведение и на практике может привести к различным катастрофическим результатам, таким как невозможность снять блокировки и , следовательно, к взаимоблокировке . [7]