|
|
Если определить функции operator new() и operator delete(), управление памятью для класса можно взять в свои руки. Это также можно, (а часто и более полезно), сделать для класса, служащего базовым для многих производных классов. Допустим, нам потребовались свои функции размещения и освобождения памяти для класса employee ($$6.2.5) и всех его производных классов:
class employee { // ... public: void* operator new(size_t); void operator delete(void*, size_t); }; void* employee::operator new(size_t s) { // отвести память в `s' байтов // и возвратить указатель на нее } void employee::operator delete(void* p, size_t s) { // `p' должно указывать на память в `s' байтов, // отведенную функцией employee::operator new(); // освободить эту память для повторного использования }
Назначение до сей поры загадочного параметра типа size_t становится
очевидным. Это - размер освобождаемого объекта. При удалении простого
служащего этот параметр получает значение sizeof(employee), а при
удалении управляющего - sizeof(manager). Поэтому собственные
функции классы для размещения могут не хранить размер каждого
размещаемого объекта. Конечно, они могут хранить эти размеры (подобно
функциям размещения общего назначения) и игнорировать параметр
size_t в вызове operator delete(), но тогда вряд ли они будут лучше,
чем функции размещения и освобождения общего назначения.
Как транслятор определяет нужный размер, который надо передать
функции operator delete()? Пока тип, указанный в operator delete(),
соответствует истинному типу объекта, все просто; но рассмотрим
такой пример:
class manager : public employee { int level; // ... }; void f() { employee* p = new manager; // проблема delete p; }
В этом случае транслятор не сможет правильно определить размер. Как и в случае удаления массива, нужна помощь программиста. Он должен определить виртуальный деструктор в базовом классе employee:
class employee { // ... public: // ... void* operator new(size_t); void operator delete(void*, size_t); virtual ~employee(); };
Даже пустой деструктор решит нашу проблему:
employee::~employee() { }
Теперь освобождение памяти будет происходить в деструкторе (а в нем размер известен), а любой производный от employee класс также будет вынужден определять свой деструктор (тем самым будет установлен нужный размер), если только пользователь сам не определит его. Теперь следующий пример пройдет правильно:
void f() { employee* p = new manager; // теперь без проблем delete p; }
Размещение происходит с помощью (созданного транслятором) вызова
employee::operator new(sizeof(manager))
а освобождение с помощью вызова
employee::operator delete(p,sizeof(manager))
Иными словами, если нужно иметь корректные функции размещения и
освобождения для производных классов, надо либо определить
виртуальный деструктор в базовом классе, либо не использовать
в функции освобождения параметр size_t. Конечно, можно было
при проектировании языка предусмотреть средства, освобождающие
пользователя от этой проблемы. Но тогда пользователь "освободился" бы
и от определенных преимуществ более оптимальной, хотя и менее надежной
системы.
В общем случае, всегда есть смысл определять виртуальный
деструктор для всех классов, которые действительно используются как
базовые, т.е. с объектами производных классов работают и, возможно,
удаляют их, через указатель на базовый класс:
class X { // ... public: // ... virtual void f(); // в X есть виртуальная функция, поэтому // определяем виртуальный деструктор virtual ~X(); };
| |