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

};