6.7 Свободная память.

   


Назад  |  Вперёд

Если определить функции 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();
           };

Назад  |  Вперёд


OtDiatlovaOU
К началу книги

Оформление и дизайн книги OtDiatlovaOU.
Вся книга, архив.