C++. Бархатный путь. Часть 2 - Функции operator new() и operator delete(). Часть 2

ОГЛАВЛЕНИЕ

 

Сложная семантика выражений C++ проявляется на простых примерах. Небольшие программы позволят выявить принципиальные моменты алгоритмов трансляции, свойства операций динамического распределения памяти, особенности операторных функций operator new() и operator delete(). В программе следует обратить внимание на второе выражение размещения, которое позволяет активизировать конструктор с параметрами.

#include <iostream.h>
#include "TypeX.h"
void main()
{
TypeX *xPoint = NULL, *xPointP = NULL, *xxPointP = NULL;
xPoint = new TypeX;
xPointP = new TypeX(25);
// Выражение размещения может содержать параметры.
// Так осуществляется управление конструктором.
xxPointP = new (125+25) TypeX(50);
// Выражение размещения может включать размещение.
// Этот одноэлементный список выражений обеспечивает передачу
// значений параметров операторной функции operator new.
// Альтернативные формы вызова операторных функций:
// ИмяТипа в круглых скобках.
// xPoint = new (TypeX);
// xPointP = new (TypeX)(25);
// xxPointP = new (125+25) (TypeX)(50);
delete xPoint;
delete xPointP;
delete xxPointP;
cout << "OK" << endl;
}

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

Так что транслируем, запускаем и наблюдаем результаты:

Это void *operator new(1)
Это TypeX()
Это void *operator new(1)
Это TypeX(25)
Это void *operator new(1, 150)
Это TypeX(50)
Это ~TypeX()
Это void operator delete(1)
Это ~TypeX()
Это void operator delete(1)
Это ~TypeX()
Это void operator delete(1)
OK

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

После этого, появляется сообщение о работе конструкторов, запуск которых обеспечивается транслятором в результате выполнения выражений размещения.

Затем, непосредственно перед выполнением выражения освобождения, выполняется деструктор, о запуске которого также заботится транслятор.

Наконец, управление передаётся операторной функции operator delete(). Жизненный цикл безымянных объектов, размещённых в динамической памяти в результате выполнения выражений размещения и адресуемых посредством указателей xPoint и xPointP, завершён.

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

/* Встроенная операторная функция operator new() */
void *operator new(size_t size)
{
cout << "Это void *operator new(" << size << ")" << endl;
return NULL;
}

Новая операторная функция даже не пытается использовать операцию выделения памяти. Она возвращает пустое значение указателя. При этом значением выражения размещения в операторе

xPoint = new TypeX;

оказывается нулевой адрес. И в результате запуск конструктора отменяется:

Это void *operator new(1)
OK

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

Мы возвращаем операторную функцию к исходному состоянию, после чего подвергнем исходную программу небольшой модификации. Расположим непосредственно перед символами операций new и delete (символ операции не обязательно представляет операцию!) разделители :: (именно разделители, поскольку они служат для модификации операции, а не используются в сочетании с операндами).

#include <iostream.h>
#include "TypeX.h"
void main()
{
TypeX *xPoint = NULL;
xPoint = ::new TypeX;
::delete xPoint;
cout << "OK" << endl;
}

В результате выполнения новой версии нашей программы мы получаем следующий результат:

Это TypeX()
Это ~TypeX()
OK

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

Это означает, что помеченные разделителем :: выражения размещения и освобождения исправно работают, выделяя и освобождая необходимую память. Символы операций ::new и ::delete воспринимаются транслятором как символы собственных "глобальных" операций выделения и освобождения памяти языка C++.

К аналогичному результату мы приходим, исключив из объявления класса TypeX объявления операторных функций operator new() и operator delete(). В этом случае перед символами операций new и delete даже не требуется располагать разделители. В этом случае транслятор их однозначно воспринимает как символы операций.

Мы снова восстанавливаем файл с объявлением класса TypeX и очередной раз модифицируем нашу программу. На этот раз мы заменим выражения размещения и освобождения выражениями явного вызова операторных функций.

#include <iostream.h>
#include "TypeX.h"
void main()
{
TypeX *xPoint = NULL;
xPoint = (TypeX *) TypeX::operator new (sizeof(TypeX));
TypeX::operator delete(xPoint, sizeof(TypeX));
// delete xPoint;
cout << "OK" << endl;
}

В результате выполнения этой версии программы на дисплей будут выведены следующие сообщения:

Это void *operator new(1)
Это void operator delete(1)
OK

Операторные функции работают успешно, память выделяется и освобождается, однако управление конструктору и деструктору не передаётся. Выражение вызова операторных функций operator new() и operator delete() не обеспечивают вызова конструктора и деструктора. Мы уже знаем, что в C++, за исключением весьма странного выражения явного вызова, вызов конструктора и деструктора обеспечивается транслятором в контексте ограниченного множества выражений. Нет соответствующего выражения, - нет и вызова конструктора.