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




Подобно члену базовый класс можно описать как частный, защищенный или общий: 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; // нормально }

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

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


Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: