Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
С++ Страуструп.doc
Скачиваний:
4
Добавлен:
18.04.2019
Размер:
2.72 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();

};

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]