реализации множественного наследования при наследовании интерфейсов.




С какими проблемами мы сталкиваемся при множественном наследовании?

v Конфликт имен

v Виртуальные методы.

Конфликт имен решается через явное указание имени базы или через приведение к ссылке на базу.

Но существует ещё одна проблема – эффективность динамического полиморфизма (виртуальных функций)

А теперь рассмотрим множественное

class A

{

public:

A(){};

virtual void a(){ a1 = 1;};

virtual void second(){..;}

int a1, a2, a3;

};

class B

{

public:

B(){};

virtual void bar(){};

virtual void bbar(){};

int b1, b2, b3;

};

class C: public A

{

public:

C(): A(){};

virtual void goo(){};// Собственная новая виртуальная функция

void a(){}; // переопределение

void bar();// переопределение

int c1;

};

….

C c;

Тут надо обратить внимание на следующее:

• Таблица виртуальных методов самого нижнего класса в иерархии доступна через первый указатель vptr.

• Каждый подобъект, который содержит виртуальные методы, имеет свою таблицу виртуальных функций.

Если в классе C переопределить метод, то в соответствующую ячейку в таблице родительского объекта будет записан указатель на новый метод. Если же в классе C добавляются новые функции – они дописываются в конец первой таблицы.

Такой алгоритм становится понятен, если рассмотреть возможные преобразования типов:

• С -> A. Через указатель на класс A можно вызывать только методы, которые прописаны в этом классе.

• C -> B. Ситуация аналогична, только мы можем вызывать виртуальные методы, определенные в классе B.

Новые виртуальные методы (которых нет в родительских классах) можно использовать только через указатель на класс C. В этом случае всегда используется первая таблица виртуальных функций.

Сложность реализации заключается в следующем:

Во время преобразования типов меняется адрес указателя:

C c;

B *p = &c;

Указатель p будет содержать адрес объекта c + смещение подобъекта B. Т.е. все вызовы методов через такой указатель будут использовать вторую таблицу виртуальных методов объекта C. Но ведь в такой ситуации при вызове переопределённой в C функции через указатель на B в эту функцию передастся неправильный указатель this! Он будет указывать не на C, как это нужно, а на B.

Приходится расширять таблицу виртуальных функций добавлением в неё смещения от указателя на объект класса до таблицы виртуальных функций для каждой функции. Если виртуальная функция из B переопределена в C, то для неё такое смещение будет равно (-смещение подобъекта B). Если же не была переопределена, то оно будет равно нулю. Для всех виртуальных функций из класса A это смещение будет нулевым, т.к. указатель на подобъект A совпадает с указателем на весь объект C(объект А находится в начале

объекта C). Теперь в функцию можно передать правильный указатель:

this = current_this + offset

где current_this – на подобъект, через который вызывается функция. offset – значение, которое берётся из расширенной таблицы виртуальных функций.

Без наследования по данным таких проблем не возникает, т.к. указатель на таблицу виртуальных функций всегда один.

Ромбовидное и не ромбовидное наследование

Не ромбовидное:: В объекте Z будет два экземпляра объекта A с разными реализациями таблицы виртуальных функций

сlass A{..;}

class X:public A{ …; }

class Y:public A{…; }

class Z: public X, public Y {…;}

Ромбовидное: В объекте Z будет только один экземпляр объекта A

сlass A{..;}

class X: public virtual A{ …; }

class Y: public virtual A{…; }

class Z: public X, public Y {…;}

13. Динамическая идентификация типа

Понятие о динамической идентификации типа (ДИТ). Достоинства и

недостатки использования ДИТ. Особенности ДИТ в современных языках

программирования.

14. Понятие о родовых объектах. Обобщенное программирование

Понятие о статической параметризации и родовых объектах.

Достоинства статической параметризации. Статическая параметризация и

ООП.

Родовые модули и подпрограммы в языке Ада.

Порождение нового пакета порождало новый экземпляр данного типа.

package Stack – два параметра – тип элемента размер стека:

generictype T is private;size: integer;package Stack isPushPopend Stack;

Можно ли так запрограммировать процедуры, чтобы они эффективно работали для любого размера стека? Конечно, да!

Конкретизация:

package IStack is new Stack(integer, 128);

Тривиальная реализация – простая макроподстановка integer и 128. Плюс – крайняя простота. Минус – очень сильное разбухание кода – сколько объявлений, столько и различных процедур.

В Аде большое разнообразие типов формальных параметров родового модуля. Формальные параметры родовых модулей:

параметр-переменная <=> любое константное выражение
type T is private <=> любой тип с операцией присваивания
type T is range <=> любой дискретный тип с упорядоченностью и функциями «следующий» и «предыдущий»
type T is delta <=> любое плавающее выражение
< > <=> при конкретизации процедуры мы можем не указывать этот вариант (параметр по умолчанию).

Пусть у нас есть:

procedure StrSort is new G_SORT(String, Integer, TARR);

C помощью < > компилятор находит функцию < для строк и подставляет как параметр по умолчанию. Родовые сегменты – абсолютно необходимая в Аде конструкция, потому что там нет передачи процедур и функций по параметру. Компилятор должен видеть не только спецификацию данной абстракции, но и тело. Гибкость повышается, но тело родовой абстракции должно быть доступно в любой момент конкретизации. В Аде-83 механизм родовых модулей – единственный, который поддерживал ОО. Как вы думаете, что самое главное, что появилось в Аде-95? Правильно, класс – тегированная запись:

type T is tagged

Механизм шаблонов в языке Си++. Шаблоны-классы и шаблоны-

функции. Параметры шаблонов. Вывод параметров шаблонов. Генерация

кода по шаблонам. Частичная специализация шаблонов. Обобщенное

программирование на языке Си++: функторы, свойства, стратегии, шаблоны

выражений.

Vector <const char *>a;Sort(a);

Будет подставлена функция < для указателей, и ошибки в программе не будет, но работать она будет неправильно.

template <class T> bool less (T& x, T& y) { return x<y;}

Это описание шаблонной функции. Хорошая функция сортировки должна работать через шаблонную функцию сравнения. Но проблема со * остаётся. Что делать? А вот что.

Пишем:

template <> bool less <const char *> (const char *p1, const char *p2) { return strcmp(p1,p2);} template <> class Vector <void *>;

Подставь вместе T void * и сгенерируй код. (?)

Для векторов возможны такие реализации:

1) template <class T> class Vector {…}
2) template <> class Vector <T*>;</br>

Vector <long > V;Vector <int> V1;

Оба по первой реализации и разные.

Vector <const char *> v2;Vector <int *>v3;

Оба по второй и одинаковые.

Будет также эффективно, как и встроенный в язык вектор. Механизм частичной специализации очень эффективен. Вспомним:

Find(It first, It last, …)

Для каждого конкретного типа можно написать частичную специализацию Find

· I == X (?)

Можно передавать третьим параметром compare, и вызывать но это неэффективно, так как это срывает работу конвейера. Выход – функтор. Перекрываем операцию ():

сlass Finder { public: bool operator ()(T&x, …) { … }}

Сравнение механизма шаблонов Си++ и родовых объектов Ады.

Особенности родовых объектов в языках С# и Java.

C#, Java – обобщенные классы

У них очень простой синтаксис и похожая идея:

class Stack <T> {…}interface IComparable <T> {…}

Параметризовать можно как классы, так и интерфейсы и методы:

public void add <T> (T x) {…}

Слово class просто синтаксически не нужно. Компилятор производит первичный синтаксический анализ и перевод в MIL/Байт-код. Конкретизация же происходит при выполнении кода. Код генерируется оптимальным образом.

В Java в качестве параметризованных типов только классы. Вместо Stack <int> S надо Stack <Integer> S

В C# на такое пойтить не могли. Там же есть структуры! Если аргумент – класс или интерфейс, код разный. Для ссылок одинаковый.

В Java: if (Stack<Integer>.getClass==Stack<String>.getClass()) даст true

В C#:

сlass Dictionary <K, V> {}

K – тип ключа, V – тип значения.

Явно при реализации должно быть нечто вроде:

public void Add(K Key, V Value) { … if (K<K1) … …}

На это будет выдана ошибка. Потому что функции < может не быть.

Надо:

if ((IComparable)K<K1)

Вместо этого можно:

class Dictionary <K, V> where K:Icomparable

Конструкция where используется в языке C# для определения ограничений на параметры родовых (другое название - обобщенных) конструкций.

Ее вид: where имя_типа: список_ограничений.

Виды ограничений:

– интерфейс — означает, что параметр-тип должен реализовывать

этот интерфейс;

– имя класса (может быть только одно такое ограничение в списке)

— означает, что параметр-тип должен быть наследником этого класса;

– struct или class – означает, что параметр-тип должен быть

структурой или классом;

– new() - означает, что параметр-тип должен иметь конструктор

умолчания (без параметров).



Поделиться:




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

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


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