Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

6.6.2 Доступ к базовым классам

      Подобно члену базовый класс можно описать как частный, защищенный       или общий:       class X {       public:       int a;       // ...       };       class Y1 : public X { };       class Y2 : protected X { };       class Y3 : private X { };       Поскольку X - общий базовый класс для Y1, в любой функции, если есть       необходимость, можно (неявно) преобразовать Y1* в X*, и притом       в ней будут доступны общие члены класса X:       void f(Y1* py1, Y2* py2, Y3* py3)       {       X* px = py1; // нормально: X - общий базовый класс Y1       py1->a = 7; // нормально       px = py2; // ошибка: X - защищенный базовый класс Y2       py2->a = 7; // ошибка       px = py3; // ошибка: X - частный базовый класс Y3       py3->a = 7; // ошибка       }       Теперь пусть описаны       class Y2 : protected X { };       class Z2 : public Y2 { void f(); };       Поскольку X - защищенный базовый класс Y2, только друзья и члены Y2,       а также друзья и члены любых производных от Y2 классов (в частности       Z2) могут при необходимости преобразовывать (неявно) Y2* в X*.       Кроме того они могут обращаться к общим и защищенным членам класса X:       void Z2::f(Y1* py1, Y2* py2, Y3* py3)       {       X* px = py1; // нормально: X - общий базовый класс Y1       py1->a = 7; // нормально       px = py2; // нормально: X - защищенный базовый класс Y2,       // а Z2 - производный класс Y2       py2->a = 7; // нормально       px = py3; // ошибка: X - частный базовый класс Y3       py3->a = 7; // ошибка       }       Наконец, рассмотрим:       class Y3 : private X { void f(); };       Поскольку X - частный базовый класс Y3, только друзья и члены Y3       могут при необходимости преобразовывать (неявно) Y3* в X*.       Кроме того они могут обращаться к общим и защищенным членам       класса X:       void Y3::f(Y1* py1, Y2* py2, Y3* py3)       {       X* px = py1; // нормально: X - общий базовый класс Y1       py1->a = 7; // нормально       px = py2; // ошибка: X - защищенный базовый класс Y2       py2->a = 7; // ошибка       px = py3; // нормально: X - частный базовый класс Y3,       // а Y3::f член Y3       py3->a = 7; // нормально       }

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