CODING // åñëè íóëåâîé óêàçàòåëü, íè÷åãî íå äåëàòü if(rawMemory == 0) return; if (size != sizeof(Base)) { ::operator delete(rawMemory); return; } // îñâîáîäèòü ïàìÿòü, íà êîòîðóþ óêàçûâàåò rawMemory; } Îïåðàòîðû new è delete ñ ðàçìåùåíèåì Функция operator new, принимающая дополнительные параметры, называется «оператором new с размещением». Обычно в качестве дополнительного параметра выступает переменная типа void*. Таким образом, определение размещающего new выглядит примерно так: void *operator new(std::size_t, void *pMemory). В более широком смысле new с размещением может принимать любое количество дополнительных параметров любого типа. Оператор delete называется «размещаемым» по такому же принципу — он тоже должен помимо основных принимать и дополнительные параметры. Теперь давай рассмотрим случай, когда мы динамически создаем объект какого-либо класса. Код такой операции должен быть всем хорошо знаком: widget *pw = new Widget. Создание объекта происходит в два этапа. На первом выделяется требуемый объем памяти стандартным оператором new, а на втором вызывается конструктор класса Widget, который инициализирует объект. Может возникнуть ситуация, когда память на первом шаге будет выделена, а конструктор возбудит исключение, и указатель *pw останется неинициализированным. Ахтунг! Таким образом мы получим потенциальную утечку памяти. Чтобы этого не произошло, за дело должна взяться система времени исполнения C++. Она обязана вызвать оператор delete для выделенной памяти на первом этапе создания объекта. Но есть один маленький нюанс, который может все испортить. C++ вызовет delete, сигнатура которого совпадает с сигнатурой new, используемого для выделения памяти. Когда мы пользуемся стандартными формами new и delete, проблем не возникает, но если мы напишем собственный new с размещением и забудем накодить соответствующую форму delete, то мы практически со стопроцентной вероятностью получим утечку памяти при возбуждении исключения в конструкторе класса. Решение этой проблемы заключается в написании оператора delete с сигнатурой, соответствующей сигнатуре new с размещением. В случае необходимости отменить выделение памяти именно этот operator delete будет вызван системой времени исполнения C++. В коде это может выглядеть так: Теперь утечек не должно быть class Widget { public: ... static void *operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); static void *operator delete(void *pMemory, std::size_t size) throw(); static void *operator delete(void *pMemory, std::ostream& logStream) throw(); ... }; Widget *pw = new (std::cerr) Widget; Не следует забывать, что сконструированный объект может быть удален стандартной формой delete. Чтобы полностью избежать всех возможных проблем, связанных с выделением памяти, следует переопределить и этот вариант функции освобождения памяти. Еще один важный момент связан с сокрытием имен функций. Если мы определим какую-либо форму new, то все остальные стандартные формы этого оператора станут недоступны. СОКРЫТИЕ ИМЕН class Base { public: static void *operator new (std::size_t size, std::ostream& logStream) throw(std::bad_alloc); … }; Такой код может вызвать утечки памяти class Widget { public: ... static void *operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc); static void *operator delete(void *pMemory, std::size_t size) throw(); ... }; Widget *pw = new (std::cerr) Widget; // Îøèáêà! Îáû÷íàÿ ôîðìà new ñêðûòà Base *pb = new Base; // Ïðàâèëüíî, âûçûâàåòñÿ ðàçìåùåííûé new èç Base Base *pb = new (std::cerr) Base; Чтобы избежать этого, можно написать формы-переходники, которые будут перенаправлять вызовы к стандартным операторам или использовать using-объявления. Çàêëþ÷åíèå На этом мы закончили разбираться с особенностями менеджмента памяти в C++ и получили порцию полезных знаний, которые пригодятся любому уважающему себя кодеру. До новых встреч в эфире! z 114 XÀÊÅÐ 09 /140/ 10
Реклама