Страница 38 из 48 Сложная семантика выражений 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++, за исключением весьма странного выражения явного вызова, вызов конструктора и деструктора обеспечивается транслятором в контексте ограниченного множества выражений. Нет соответствующего выражения, - нет и вызова конструктора. |