18.3. Открытое, закрытое и защищенное наследование
18.3. Открытое, закрытое и защищенное наследование
Открытое наследование называется еще наследованием типа. Производный класс в этом случае является подтипом базового; он замещает реализации всех функций-членов, специфичных для типа базового класса, и наследует общие для типа и подтипа функции. Можно сказать, что производный класс служит примером отношения "ЯВЛЯЕТСЯ", т.е. предоставляет специализацию более общего базового класса. Медведь (Bear) является животным из зоопарка (ZooAnimal); аудиокнига (AudioBook) является предметом, выдаваемым читателям (LibraryLendingMaterial). Мы говорим, что Bear – это подтип ZooAnimal, равно как и Panda. Аналогично AudioBook – подтип LibBook (библиотечная книга), а оба они – подтипы LibraryLendingMaterial. В любом месте программы, где ожидается базовый тип, можно вместо него подставить открыто унаследованный от него подтип, и программа будет продолжать работать правильно (при условии, конечно, что подтип реализован корректно). Во всех приведенных выше примерах демонстрировалось именно наследование типа.
Закрытое наследование называют также наследованием реализации. Производный класс напрямую не поддерживает открытый интерфейс базового, но пользуется его реализацией, предоставляя свой собственный открытый интерфейс.
Чтобы показать, какие здесь возникают вопросы, реализуем класс PeekbackStack, который поддерживает выборку из стека с помощью метода peekback():
bool
PeekbackStack::
peekback( int index, type &value ) { ... }
где value содержит элемент в позиции index, если peekback() вернула true. Если же peekback() возвращает false, то заданная аргументом index позиция некорректна и в value помещается элемент из вершины стека.
* В реализации PeekbackStack возможны два типа ошибок: реализация абстракции PeekbackStack: некорректная реализация поведения класса;
* реализация представления данных: неправильное управление выделением и освобождением памяти, копированием объектов из стека и т.п.
Обычно стек реализуется либо как массив, либо как связанный список элементов (в стандартной библиотеке по умолчанию это делается на базе двусторонней очереди, хотя вместо нее можно использовать вектор, см. главу 6). Хотелось бы иметь гарантированно правильную (или, по крайней мере, хорошо протестированную и поддерживаемую) реализацию массива или списка, чтобы использовать ее в нашем классе PeekbackStack. Если она есть, то можно сосредоточиться на правильности поведения стека.
У нас есть класс IntArray, представленный в разделе 2.3 (мы временно откажемся от применения класса deque из стандартной библиотеки и от поддержки элементов, имеющих отличный от int тип). Вопрос, таким образом, заключается в том, как лучше всего воспользоваться классом IntArray в нашей реализации PeekbackStack. Можно задействовать механизм наследования. (Отметим, что для этого нам придется модифицировать IntArray, сделав его члены защищенными, а не закрытыми.) Реализация выглядела бы так:
#include "IntArray.h"
class PeekbackStack : public IntArray {
private:
const int static bos = -1;
public:
explicit PeekbackStack( int size )
: IntArray( size ), _top( bos ) {}
bool empty() const { return _top == bos; }
bool full() const { return _top == size()-1; }
int top() const { return _top; }
int pop() {
if ( empty() )
/* ia?aaioaou ioeaeo */ ;
return _ia[ _top-- ];
}
void push( int value ) {
if ( full() )
/* ia?aaioaou ioeaeo */ ;
_ia[ ++_top ] = value;
}
bool peekback( int index, int &value ) const;
private:
int _top;
};
inline bool
PeekbackStack::
peekback( int index, int &value ) const
{
if ( empty() )
/* ia?aaioaou ioeaeo */ ;
if ( index 0 || index _top )
{
value = _ia[ _top ];
return false;
}
value = _ia[ index ];
return true;
}
К сожалению, программа, которая работает с нашим новым классом PeekbackStack, может неправильно использовать открытый интерфейс базового IntArray:
extern void swap( IntArray&, int, int );
PeekbackStack is( 1024 );
// iai?aaaeaaiiia ioeai?iia eniieuciaaiea PeekbackStack
swap(is, i, j);
is.sort();
is[0] = is[512];
Абстракция PeekbackStack должна обеспечить доступ к элементам стека по принципу "последним пришел, первым ушел". Однако наличие дополнительного интерфейса IntArray не позволяет гарантировать такое поведение.
Проблема в том, что открытое наследование описывается как отношение "ЯВЛЯЕТСЯ". Но PeekbackStack не является разновидностью массива IntArray, а лишь включает его как часть своей реализации. Открытый интерфейс IntArray не должен входить в открытый интерфейс PeekbackStack.
Закрытое наследование от базового класса представляет собой вид наследования, который нельзя описать в терминах подтипов. В производном классе открытый интерфейс базового становится закрытым. Все показанные выше примеры использования объекта PeekbackStack становятся допустимыми только внутри функций-членов и друзей производного класса.
В приведенном ранее определении PeekbackStack достаточно заменить слово public в списке базовых классов на private. Внутри же самого определения класса public и private следует оставить на своих местах:
class PeekbackStack : private IntArray { ... };
18.3.1. Наследование и композиция
Реализация класса PeekbackStack с помощью закрытого наследования от IntArray работает, но необходимо ли это? Помогло ли нам наследование в данном случае? Нет.
Открытое наследование – это мощный механизм для поддержки отношения "ЯВЛЯЕТСЯ". Однако реализация PeekbackStack по отношению к IntArray – пример отношения "СОДЕРЖИТ". Класс PeekbackStack содержит класс IntArray как часть своей реализации. Отношение "СОДЕРЖИТ", как правило, лучше поддерживается с помощью композиции, а не наследования. Для ее реализации надо один класс сделать членом другого. В нашем случае объект IntArray делается членом PeekbackStack. Вот реализация PeekbackStack на основе композиции:
class PeekbackStack {
private:
const int static bos = -1;
public:
explicit PeekbackStack( int size ) :
stack( size ), _top( bos ) {}
bool empty() const { return _top == bos; }
bool full() const { return _top == size()-1; }
int top() const { return _top; }
int pop() {
if ( empty() )
/* обработать ошибку */ ;
return stack[ _top-- ];
}
void push( int value ) {
if ( full() )
/* обработать ошибку */ ;
stack[ ++_top ] = value;
}
bool peekback( int index, int &value ) const;
private:
int _top;
IntArray stack;
};
inline bool
PeekbackStack::
peekback( int index, int &value ) const
{
if ( empty() )
/* обработать ошибку */ ;
if ( index 0 || index _top )
{
value = stack[ _top ];
return false;
}
value = stack[ index ];
return true;
}
* Решая, следует ли использовать при проектировании класса с отношением "СОДЕРЖИТ" композицию или закрытое наследование, можно руководствоваться такими соображениями: если мы хотим заместить какие-либо виртуальные функции базового класса, то должны закрыто наследовать ему;
* если мы хотим разрешить нашему классу ссылаться на класс из иерархии типов, то должны использовать композицию по ссылке (мы подробно расскажем о ней в разделе 18.3.4);
* если, как в случае с классом PeekbackStack, мы хотим воспользоваться готовой реализацией, то композиция по значению предпочтительнее наследования. Если требуется отложенное выделение памяти для объекта, то следует выбрать композицию по ссылке (с помощью указателя).
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Защищенное хранилище
Защищенное хранилище Служба является посредником между частью оперативной памяти, содержащей пароли пользователей, сеансовые билеты и другую критически важную для безопасности компьютера информацию (информация хранится в виде хэша), и программами, которым нужна для
2. Наследование
2. Наследование Процесс, с помощью которого один тип наследует характеристики другого типа, называется наследованием. Наследник называется порожденным (дочерним) типом, а тип, которому наследует дочерний тип, называется порождающим (родительским) типом.Ранее известные
37. Открытое наследование означает заменимость. Наследовать надо не для повторного использования, а чтобы быть повторно использованным
37. Открытое наследование означает заменимость. Наследовать надо не для повторного использования, а чтобы быть повторно использованным РезюмеОткрытое наследование позволяет указателю или ссылке на базовый класс в действительности обращаться к объекту некоторого
Правило 32: Используйте открытое наследование для моделирования отношения «является»
Правило 32: Используйте открытое наследование для моделирования отношения «является» Вильям Демент (William Dement) в своей книге «Кто-то должен бодрствовать, пока остальные спят» (W. H. Freeman and Company, 1974) рассказывает о том, как он пытался донести до студентов наиболее важные идеи
Правило 34: Различайте наследование интерфейса и наследование реализации
Правило 34: Различайте наследование интерфейса и наследование реализации Внешне простая идея открытого наследования при ближайшем рассмотрении оказывается состоящей из двух различных частей: наследования интерфейса функций и наследования их реализации. Различие
1.1.2. Наследование
1.1.2. Наследование Мы подходим к одной из самых сильных сторон ООП — наследованию. Наследование —- это механизм, позволяющий расширять ранее определенную сущность путем добавления новых возможностей. Короче говоря, наследование - это способ повторного использования
Наследование
Наследование Следующим принципом ООП является наследование, означающее способность языка обеспечить построение определений новых классов на основе определений существующих классов. В сущности, наследование позволяет расширить возможности поведения базового класса
18.3.3. Защищенное наследование
18.3.3. Защищенное наследование Третья форма наследования – это защищенное наследование. В таком случае все открытые члены базового класса становятся в производном классе защищенными, т.е. доступными из его дальнейших наследников, но не из любого места программы вне
18.5. Виртуальное наследование A
18.5. Виртуальное наследование A По умолчанию наследование в C++ является специальной формой композиции по значению. Когда мы пишем:class Bear : public ZooAnimal { ... };каждый объект Bear содержит все нестатические данные-члены подобъекта своего базового класса ZooAnimal, а также нестатические
Наследование
Наследование Пожалуй, самая важная возможность, предоставляемая программисту средствами языка Си++, заключается в механизме наследования. Вы можете наследовать от определенных ранее классов новые производные классы. Класс, от которого происходит наследование,
Наследование
Наследование Класс может быть унаследован от другого класса. Класс, от которого наследуют, называют базовым классом (надклассом, предком), а класс, который наследуется, называется производным классом (подклассом, потомком). При наследовании все поля, методы и свойства
Дублируемое наследование
Дублируемое наследование Дядюшка Жак: С кем желаете Вы говорить, сударь, с конюхом или с поваром? Ибо я у Вас и то, и другое. Мольер, "Скупой" Дублируемое наследование (repeated inheritance) возникает, когда класс является потомком другого класса более чем на одном пути наследования.
Защищенное проставление меток времени
Защищенное проставление меток времени Защищенное датирование, или проставление меток времени , заключается в связывании доверенным центром датирования метки времени с определенной "порцией" данных при сохранении их аутентичности и целостности. Причем, важным
26. Наследование
26. Наследование Наследование – это процесс порождения новых типов-потомков от существующих типов-родителей, при этом потомок получает (наследует) от родителя все его поля и методы.Тип-потомок, при этом, называется наследником или порожденным (дочерним) типом. А тип,