Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции / LECS17.DOC
Скачиваний:
44
Добавлен:
16.04.2013
Размер:
151.55 Кб
Скачать

Шаблоны и наследование

Вместе шаблоны и наследование являются исключительно мощной техникой повторного использования кода. Параметризованные типы могут использоваться повторно посредством наследования. Такое использование аналогично использованию наследования обычных типов. И шаблоны, и наследование – это механизмы для повторного использования кода, и оба они обеспечивают полиморфизм. Они являются двумя разными чертами С++ и комбинируются в различных формах. Шаблонный класс может быть произведен от обычного класса, обычный класс – инстанцированного шаблонного класса, шаблонный класс можно произвести от шаблонного класса. Каждая из этих возможностей ведет к различным отношениям.

В некоторых ситуациях шаблоны приводят к неприемлемым издержкам – избыточному размеру объектного модуля. Каждый инстанцированный шаблонный класс нуждается в своем откомпилированном объектном модуле. Рассмотрим класс gen_tree из раздела 3, «Повторное использование кода: класс двоичного дерева». Он обеспечивал повторное использование, предоставляя код, легко преобразуемый с помощью наследования и приведения к полезным типам указателей. Недостаток типа gen_tree состоит в том, ч то каждый тип указателя нуждался в отдельном кодировании определения его класса. Этот недостаток можно устранить, используя шаблон для наследования базового класса:

//Базовый класс для обеспечения небольшого размера кода

template <class T>

class pointer_tree : privat gen_tree {

public:

pointer_tree() {}

void insert (T* d) { gen_tree::insert(d); }

T* find(T* d) const

{ return reinterpret_cast<T*> (gen_tree::find(d)); }

void print() { gen_tree::print(); }

};

Объективный код для gen_tree:: относительно велик, но он используется только один раз. Интерфейс pointer_tree <тип> нуждается лишь в небольшом объектном модуле для каждого инстанционирования. Это существенно экономичнее по сравнению решением на основе шаблонов.

Получение производного класса от инстанцированного шаблонного класса в основном не отличается от обычного наследования. В следующем примере мы повторно используем stack<char> в качестве базового класса для безопасного стека символов:

В файле stack_t2.cpp

//Безопасный стек символов

#include <assert.h>

class safe_char_stack:public stack<char> {

public:

//проверка push и pop

void push(char c)

{ assert (!full());stack<char>: push©; }

char pop ()

{assert (!empty(); return (stack<char>::pop();}

};

Инстанцированный класс stack<char> создается и используется повторно с помощью safe_char_stack.

Этот пример можно обобщить до шаблонного класса:

В файле stack_t3.cpp

//Параметризованный безопасный стек

template <class TYPE>

class safe_stack : public stack<TYPE> {

public:

void push (TYPE c)

{ assert (!full()); stack<TYPE>::push(c); }

TYPE pop()

{assert(!empty()); retyrn (stack<TYPE>::push()) }

};

Важно отметить связь между базовым классом и производным классом. Оба нуждаются в доном и том же инстанцированном типе. Каждая пара базового и производного классов не зависит от всех остальных пар.

Множественное наследование.

До сих пор примеры в тексте требовали лишь одиночного наследования (single inheritance): Класс производится от единственного базового класса. Это может привести к цепочке наследований, когда класс В производится от класса А, класс С – от класса В, …, а класс N – от класса М. В результате N завершает цепь, имея в основе А, В, …, М. Однако эта цепочка не должна быть замкнутой – класс не может быть предком самого себя.

Множественное наследование (multiple inheritance) делает возможным получение производного класса от более чем одного базового класса. Синтаксис заголовка класса расширяется, чтобы можно было использовать список базовых классов с атрибутами доступа. Например:

class student { //студент

…………

};

class worker { //рабочий

…………

};

class student_worker: public student, public worker {

………… //работающий студент

};

В этом примере производный класс student_worker открыто наследует члены обоих базовых классов. Такие родительские отношения описываются ориентированным циклическим графом (directed acyclic grash – DAG) наследования. Ориентированный ациклический граф наследования – это граф, узлы которого являются классами, а ориентированные ребра направлены от производных классов к базовым. Он не может содержать циклов, так как никакой класс не может через цепочку наследований происходить сам от себя.

При производстве от различных классов членов с идентичными именами могут возникать неопределенности. Подобные наследования допускаются при условии, что пользователь не делает двусмысленных ссылок на такой член. Например:

plass worker {

public:

const int soc_sec;

const char* name;

……………

};

class student_worker: public student, public worker {

public:

void print() { count << “код соц. защиты:”<<soc_sec<<

“\n”;

cout << name; …………… } //ошибка

……………

};

На теле функции student_worker:: print()ссылка на soc_sec сработает, но ссылка на двусмысленна. Двусмысленность можно преодолеть, правильно задав имя с помощью оператора разрешения области видимости.

При множественном наследовании два базовых класса могут быть получены от общего предка. Если оба базовых класса используются обычным образом свои производным классом, такой класс будет иметь два подобъекта общего предка. Если подобное дублирование нежелательно, его можно избежать с помощью виртуального наследования. Вот пример:

class student: public virtual person {

……………

};

class worker: public virtual person {

……………

};

class student_worker: public student, public worker {

……………

};

Без использования виртуальности в этом примере класс включал бы объекты «class::student::person»и «class::worker::person». Ниже приведен порядок выполнения конструкторов при наследовании и при инициализации членов класса:

Соседние файлы в папке Лекции