Бьерн Страуструп - Язык программирования С++. Главы 5-8
Страница 46. Свободная память



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();
          };

 
« Предыдущая статья   Следующая статья »