Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
При определении собственной версии функции operator new() можно определить дополнительные параметры. Чтобы использующие такие функции выражения new могли передать аргументы этим дополнительным параметрам, следует применять размещающую форму оператора new (см. раздел 12.1.2). Хотя обычно вполне можно определить собственную версию функции operator new(), чтобы получить необходимый набор параметров, нельзя определить эту функцию в следующей форме:
void *operator new(size_t, void*); // эта версия не может быть
// переопределена
Данная конкретная форма зарезервирована для использования библиотекой и не может быть переопределена.
У функций operator delete() и operator delete[]() должен быть тип возвращаемого значения void и первый параметр типа void*. Выполнение выражения delete вызывает соответствующую функцию оператора и инициализирует ее параметр типа void* указателем на область памяти, подлежащую освобождению.
Когда функции operator delete() и operator delete[]() определяются как члены класса, у них может быть второй параметр типа size_t. Этот дополнительный параметр инициализируется размером (в байтах) объекта, заданного первым параметром. Параметр типа size_t используется при удалении объектов, являющихся частью иерархии наследования. Если у базового класса есть виртуальный деструктор (см. раздел 15.7.1), то передаваемый функции operator delete() размер зависит от динамического типа объекта, на который указывает удаляемый указатель. Кроме того, выполняемая версия функции operator delete() также будет зависеть от динамического типа объекта.
Терминология. Выражение new или функция operator new()Имена библиотечных функций operator new() и operator delete() могут ввести в заблуждение. В отличие от других функций операторов (таких как operator=), эти функции не перегружают операторы new и delete. Фактически переопределить поведение операторов new и delete нельзя.
В процессе выполнения оператор new вызывает функцию operator new(), чтобы зарезервировать область памяти, в которой он затем создает объект. Оператор delete удаляет объект, а затем вызывает функцию operator delete(), чтобы освободить использованную объектом память.
Функции malloc() и free()Если определяются собственные глобальные функции operator new() и operator delete(), они должны резервировать и освобождать память так или иначе. Даже если эти функции определяются для использования специализированной системы резервирования памяти, может иметь смысл (для проверки) иметь способность резервировать память тем же способом, что и обычная реализация.
В этом случае можно использовать функции malloc() и free(), унаследованные языком С++ от языка С. Они определяются в заголовке cstdlib.
Функция malloc() получает параметр типа size_t, задающий количество резервируемых байтов. Она возвращает указатель на зарезервированную область памяти или значение 0, если зарезервировать память не удалось. Функция free() получает параметр типа void*, являющийся копией указателя, возвращенного функцией malloc(), и возвращает занятую память операционной системе. Вызов free(0) не делает ничего.
Вот простейший код функций operator new() и operator delete():
void *operator new(size_t size) {
if (void *mem = malloc(size))
return mem;
else
throw bad_alloc();
}
void operator delete(void *mem) noexcept { free(mem); }
Для других версий функции operator new() и operator delete() код аналогичен.
Упражнения раздела 19.1.1Упражнение 19.1. Напишите собственную версию функции operator new(size_t), используя функцию malloc(), и версию функции operator delete(void*), используя функцию free().
Упражнение 19.2. По умолчанию класс allocator использует функцию operator new() для резервирования места и функцию operator delete() для ее освобождения. Перекомпилируйте и повторно запустите программу StrVec (см. раздел 13.5), используя собственные версии функций из предыдущего упражнения.
19.1.2. Размещающий оператор new
Хотя функции operator new() и operator delete() предназначены для использования выражениями new, они являются обычными библиотечными функциями. Поэтому обычный код вполне может вызвать их непосредственно.
В прежних версиях языка (до того, как класс allocator (см. раздел 12.2.2) стал частью библиотеки), когда необходимо было отделить резервирование от инициализации, использовались функции operator new() и operator delete(). Эти функции ведут себя аналогично функциям-членам allocate() и deallocate() класса allocator — резервируют и освобождают память, но не создают и не удаляют объекты.
В отличие от класса allocator, нет функции construct(), позволяющей создавать объекты в памяти, зарезервированной функцией operator new(). Вместо этого для создания объекта используется размещающий оператор new (placement new) (см. раздел 12.1.2). Как уже упоминалось, эта форма оператора new предоставляет дополнительную информацию функции резервирования. Размещающий оператор new можно использовать для передачи адреса области. Тогда выражения размещающего оператора new будут иметь следующую форму:
new (адрес_области) тип
new (адрес_области) тип (инициализаторы)
new (адрес_области) тип [размер]
new (адрес_области) тип [размер] { список инициализации }
где адрес_области является указателем, а инициализаторы представляют собой разделяемый запятыми список инициализаторов (возможно, пустой), используемый для создания вновь зарезервированного объекта.
Будучи вызванным с адресом, но без других аргументов, размещающий оператор new использует вызов operator new(size_t, void*) для "резервирования" памяти. Эта версия функции operator new() не допускает переопределения (см. раздел 19.1.1). Она не резервирует память, а просто возвращает свой аргумент указателя. Затем обычное выражение new заканчивает свою работу инициализацией объекта по данному адресу. В действительности размещающий оператор new позволяет создать объект в заданной адресом предварительно зарезервированной области памяти.
При передаче одного аргумента, являющегося указателем, выражение размещающего оператора new создает объект, но не резервирует память.
Хотя существует несколько способов использования размещающего оператора new, он похож на функцию-член construct() класса allocator, но с одним важным отличием. Передаваемый функции construct() указатель должен указывать на область, зарезервированную тем же объектом класса allocator. Указатель, передаваемый размещающему оператору new, не обязан указывать на область памяти, зарезервированной функцией operator new(). Как будет продемонстрировано в разделе 19.6, переданный выражению размещающего оператора new указатель даже не обязан указывать на динамическую память.
Явный вызов деструктораПодобно тому, как размещающий оператор new является низкоуровневой альтернативой функции-члену allocate() класса allocator, явный вызов деструктора аналогичен вызову функции destroy().
Вызов деструктора происходит таким же образом, как и любой другой функции-члена объекта: через указатель или ссылку на объект:
string *sp = new string("a value"); // резервирует и инициализирует
// строку
sp->~string();
Здесь деструктор вызывается непосредственно. Для получения объекта, на который указывает указатель sp, используется оператор стрелки. Затем происходит вызов деструктора, имя которого совпадает с именем типа, но с предваряющим знаком тильды (~).
Подобно вызову функции destroy(), вызов деструктора освобождает заданный объект, но не освобождает область, в которой располагается этот объект. При желании эту область можно использовать многократно.
Вызов деструктора удаляет объект, но не освобождает память.
19.2. Идентификация типов времени выполнения
Идентификацию типов времени выполнения (run-time type identification RTTI) обеспечивают два оператора.
• Оператор typeid, возвращающий фактический тип заданного выражения.