Лекции (1129116), страница 28

Файл №1129116 Лекции (Лекции) 28 страницаЛекции (1129116) страница 282019-05-11СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 28)

}

class Z {

int j;

}

class Y: public X {

int k;

}

class W: public X, public Y, public Z {

int l;

}

С
интаксис множественного наследования очень похож на единичное наследование. Спецификатор public (и другие спецификаторы) имеет ту же семантику, что и при единичном наследовании. Объект класса W обладает полями классов X, Y, Z. Кроме того, содержит свои поля.

К
аким образом будет распределяться память под объект? Мы уже говорили, что есть два подхода распределения памяти: цепной и линейный. Для множественного наследования в С++ более эффективен линейный подход. При цепном подходе, объект класса-наследника должен содержать не просто ссылку на базовый класс, а целую таблицу ссылок. Хотя и линейная схема распределения памяти в С++ дает некоторые накладные расходы. Большинство компиляторов разместит объекты друг за другом.

В объекте класса W будет находиться два экземпляра класса Х. При этом возникает конфликт имен между классами Х и XY. Явным образом указать класс (Х или XY), к которому мы хотим обратиться, в данном случае нельзя, потому что запись X::i не определяет, к какому из двух классов X необходимо обратиться. Компилятор, в данном случае, выбирает правило доминирования имен: имя A::i доминирует над именем B::i в том случае, если A содержит класс B в качестве одного из своих базовых классов. Это правило доминирования будет действовать и при выборе виртуальных функций.

В данном случае, X::i будет означать обращение к верхнему в схеме объекту Х (т.е. к прямому предку). К полю объекта XY можно обратиться и через класс Y (Y::i), но только если это поле в классе Y не переопределено. Иначе к полю объекта XY обратиться невозможно.

Невозможно реализовать следующий вариант наследования:

class X: public Y, public Y{…}

В данном случае было бы невозможно из класса Х обратиться к какому-либо полю Y, потому что оба предка равноправны. Можно было бы специально для этого ввести механизм различения таких классов, но Страуструп избегал такого рода подходов.

Как реализовать ромбовидную схему наследования? Каким образом сделать так, чтобы классы Y и Z были наследниками одного экземпляра объекта Х? Определение класса W выглядит следующим образом:

c
lass W: public Y, public Z{…}; //определение класса W ничем не отличается

//классы Y и Z должны быть определены иначе.

class Y: virtual public X{…};

class Z: virtual public X{…};

Классы Y и Z должны наследовать класс Х виртуальным образом. Если один из этих классов наследует не виртуальным образом, то ромбовидная структура не получиться, и будет два экземпляра Х.

Если затем, например, описать класс А следующим образом:

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

то объект этого класса будет содержать два тела Х, одно из которых совместно используется классами Y и Z. Если же мы хотим, чтобы было только одно тело Х, то нужно писать иначе:

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

В каких случаях удобно использовать множественное наследование? Для первой схемы рисунка (левая часть неравенства) можно привести следующий пример. Представим себе, что Х – это обобщенный контейнерный класс, например, линейный список. У контейнерного класса есть набор функций для работы с ним. Для того, чтобы хранить что-то в соответствующем списке, мы должны были добавлять к этим функциям конкретные данные, наследуя контейнерный класс. В данном случае, наличие двух объектов Х означает, что мы хотим, чтобы класс W находился сразу в двух линейных списках (например, список графических объектов на экране и список объектов, которые в данный момент не должны рисоваться).

З
ачем нужна ромбовидная форма наследования? Эта форма имеет несколько иную карту распределения памяти. Классы Y и Z совместно используют одно тело X. В стандартной библиотеке С++ есть пример такой схемы наследования, потому что, когда Страуструп вводил некоторые абстракции, он должен был их оправдать на примере стандартной библиотеки.

В данном случае, имеется в виду библиотека надежного ввода-вывода iostream. Есть некоторый базовый тип stream (аналог Х), из которого наследуются типы istream и ostream (аналогично классам Y и Z), которые являются базовыми классами для типа iostream (соответственно классу W). Получается ромбовидная схема наследования. Класс stream содержит в себе некий буфер, файловый дескриптор и др. Классы istream и ostream содержат интерфейс для ввода и вывода соответственно. Класс iostream применяется и для ввода и для вывода, причем через один и тот же буфер и один файловый дескриптор. Если бы для чтения и для записи использовались бы разные буфера, то ввод-вывод был бы некорректен.

На первый взгляд, множественное наследование добавляет некоторые преимущества, потому что что-то можно делать, чего раньше делать было нельзя. Хотя все это можно моделировать и с помощью единичного наследования, используя отношение включения. На первый взгляд, эффективная реализация множественного наследования сложности не представляет, но на самом деле существует одна большая проблема, связанная с виртуальными функциями. Множественное наследование было добавлено в С++ в районе 85-86го года, и тогда перед Страуструпом стояла проблема: нужно было срочно довести язык до квазипромышленной реализации, компилятор которой, можно было уже лицензировать. И на него оказывалось менеджерское давление. Перед Страуструпом стояло два вопроса: работать ли над множественным наследованием или работать над шаблонами. Страуструп говорит, что самая большая ошибка, которую он допустил при работе над языком С++, это то, что он занялся реализацией множественного наследования в ущерб статической параметризации.

Проблема здесь не столько в спорном характере множественного наследования, сколько в том, что было упущено время, следующая версия вышла только через четыре года. Если бы шаблоны были бы внедрены в язык в 86-ом году, то к 90-му году уже появилась бы работоспособная и стандартная библиотека шаблонов. Заметьте, что сейчас ситуация такова, что каждая фирма-производитель выпускает свою шаблонную библиотеку. У MFC один набор шаблонов, у Borland другой набор шаблонов. Правда в середине 90-х годов появилась таки библиотека STL (Standard Template Library), однако она появилась, когда поезд уже ушел, и программисты уже не хотят переучиваться.

Язык Java использует множественное наследование только для специальных видов классов, а именно, для классов, которые состоят только из функций. На первый взгляд, кажется, что такие классы смысла не имеют. Возникает вопрос: если нет данных, то с чем же будут работать функции этого класса? Но если это статические функции, то такой класс приобретает смысл модуля. Наследование такого модуля полезно, когда соответствующие функции становятся динамически связываемыми (см. пример на Обероне с рисованием графических объектов).

На этом мы пока закончим тему множественного наследования, чтобы потом к ней вернуться, рассматривая абстрактные классы языка С++ и интерфейсы языка Java. Именно для этих целей и требуется множественное наследование.

Глава 2. Динамическое связывание методов.

Мы начнем с языка Оберон, потому что это самый простой язык из нами рассматриваемых. Вспомним пример, в котором мы определяли указатель Shape на некоторую структуру данных Node. Из этой структуры наследовались типы PointObj, LineObj и т.д., для которых существовали соответствующие указатели Point, Line и т.д. То есть наследование базовых типов переносилось на указатели. В качестве примера работы с этими объектами, мы написали процедуру Draw, которая рисовала все объекты из списка типа Shape. Гибкой реализации этой процедуры мы добились только тогда, когда определили интерфейс для Shape, в котором были обработчики Draw, Move и т.д. Для каждого объекта был определен соответствующий обработчик (т.е. для каждого объекта значение указателя на процедуру было разным), и при инициализации соответствующего объекта инициализировался соответствующий обработчик. Т.е. мы в каждом объекте переопределяли данный обработчик. В этом и заключается суть динамического связывания методов. Главный обработчик выглядел так: this.Draw(this). И мы не могли сказать, глядя на эту запись, какой же именно объект будет рисоваться.

Какие недостатки у такого метода? Во-первых, это потенциальная ненадежность, потому что мы можем испортить соответствующую структуру данных Shape. Мы можем присвоить не тот метод Draw для данного объекта. При этом весь контроль ложиться на программиста. К тому же в программе не должно существовать объектов типа Shape, потому что для них нет смысла определять обработчик. В языке должна быть исключена возможность создания объектов типа Shape.

Поэтому появилось некоторое расширение языка Оберон, а именно Оберон-2. Оберон-2 – это уже настоящий объектно-ориентированный язык, по мощности сравнимый с С++. К этому языку были добавлены процедуры, динамически привязанные к типу.

Суть динамического привязывания состоит в следующем. Мы для некоторого базового класса определяется интерфейс, который может выглядеть так:

TYPE NODE = RECORD

X, Y : INTEGER;

NEXT : SHAPE;

END;

Существует еще некоторый процедурный интерфейс который, например, может содержать процедуру включения в список фигуры:

PROCEDURE ADD (S : SHAPE; VAR L : SHAPE);

Эта процедура не меняется в зависимости от фигуры, т.е. в терминологии языка С++, она не является виртуальной. Есть еще другой функциональный интерфейс, а именно, процедуры, динамически привязанные к типу:

PROCEDURE (P:SHAPE) Draw();

Эта процедура по синтаксису выглядит иначе. В данном случае, она не имеет параметров, а параметр Р играет роль указателя this. Чем отличаются такие процедуры по своей семантике от обычных? В языке Оберон (и Оберон-2) никакого статического перекрытия нет, т.е. отсутствует статический полиморфизм. Имя процедуры в пределах модуля должно быть уникально. Для процедур привязанных к типу все наоборот: они должны быть переопределены. Допустим мы выводим новый тип данных из SHAPE:

TYPE PointObj = RECORD(NODE)

END;

Point = POINTER TO PointObj;

PROCEDURE (P:Point) Draw()

BEGIN

END Draw;

Этот тип данных ничего, с точки зрения данных не дает, но зато он наследует всю функциональность типа NODE. Мы должны переопределить процедуру Draw, для того, чтобы она вызывалась для объектов типа Point. Тоже самое делается для типов данных Line, Circle и др. Как работать с этой процедурой?

VAR L:SHAPE;

….

L.Draw();

Этот вызов вызывает процедуру, динамически привязанную к типу SHAPE. Т.е. в зависимости от динамического типа L вызывается соответствующий вариант этой процедуры. Заметьте, что в данном случае, уже никаких процедурных переменных не используется, отсутствует инициализация. Все делается автоматически соответствующей Run-Time системой. При этом компилятор гарантирует надежность, т.е. программисту в теле соответствующего обработчика не требуется писать проверок.

С точки зрения межмодульного интерфейса в языке Оберон есть соответствующие инструменты, которые позволяют сгенерировать интерфейсное описание модуля DEFENITION. Интересно, что DEFENITION для типа NODE будет выглядеть следующим образом:

TYPE NODE = RECORD

PROCEDURE Draw;

END;

PROCEDURE ADD();

Обычные процедуры будут выписаны отдельно. А динамически привязанные методы являются как бы частью класса NODE.

Когда мы пишем L.Draw(), то как раз L и выступает как соответствующий аргумент. А в реализации Draw мы используем Р как указатель this. Естественно, что процедура DrawAll() будет выглядеть почти точно также:

WHILE this # NIL DO

this.Draw();

this := this.NEXT;

END;

Теперь не требуется передавать обработчику соответствующего параметра. Т.е. в гибкости мы ничего не потеряли, а приобрели в надежности. И это за счет того, что механизм динамической привязки был реализован как часть языка.

Давайте рассмотрим язык С++. Аналогично, в С++ все функции-члены делятся на два класса: виртуальные и невиртуальные. Виртуальные функции и являются аналогом процедур, динамически привязанных к типу. Рассмотрим пример, который поясняет принцип работы виртуальных функций.

class X{

virtual int f(){cout << "X::f";}

int g(){cout << "X::g";}

}

Пусть есть еще один класс:

class Y: public X{

virtual int f(){cout << "Y::f";} // слово virtual необязательно, функция уже виртуальна

int g(){cout << "Y::g";}

}

X* px = new X;

Y* py = new Y;

px->f(); px->g(); // X::f X::g

py->f(); py->g(); // Y::f Y::g

px = py; //Меняем динамический тип px

px->f(); px->g(); // Y::f X::g

Заметим, что в языке Turbo Pascal, и у его наследника Delphi тоже есть понятие виртуальных методов. Но используется несколько иная терминология. В Turbo Pascal виртуальные методы назывались виртуальными, а невиртуальные – назывались статическими. В С++ статические методы – это совсем другое. В Turbo Pascal статических методов в смысле С++ нет. Но есть не только синтаксические различия.

type MyObj = class

procedure F; virtual;

Характеристики

Тип файла
Документ
Размер
1,12 Mb
Материал
Тип материала
Высшее учебное заведение

Список файлов лекций

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
6314
Авторов
на СтудИзбе
312
Средний доход
с одного платного файла
Обучение Подробнее