|
|||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Типовой пример применения множественного наследования — «наблюдатель» (observer)
16. Виртуальные методы в языке C++. Недостатки синтаксиса определения и перекрытия виртуальных методов в языке C++. Понятие константного метода. Проблемы, порождаемые наличием константных методов. Операторы приведения типа в языке C++: const_cast, reinterpret_cast, static_cast, dynam-ic_cast. Виртуальные методы в языке C++ В С++ виртуальные методы определяются при помощи ключевого слова virtualю.
При перекрытии виртуального метода ключевое слово virtual можно записать, а можно и опустить. Синтаксис перекрытия виртуальных методов не предусматривает такие проблемы, как версионность и рефакторинг кода (упрощение программного кода с сохранением функциональности). Если метод виртуальный следует всегда писать ключевое слово virtual.
Недостатки синтаксиса определения и перекрытия виртуальных методов в языке C++
Таблица виртуальных функций хранит адреса виртуальных функций, объявленных для объектов данного класса. Например, объект базового класса содержит указатель на таблицу адресов всех виртуальных функций для этого класса. Объект производного класса содержит указатель на отдельную таблицу адресов. Если производный класс дает новое определение виртуальной функции, то в таблице виртуальных функций сохраняется адрес новой функции. Если же производный класс не переопределяет виртуальную функцию, таблица виртуальных функций хранит адрес исходной версии функции. Если производный класс определяет новую функцию и объявляет ее виртуальной, ее адрес добавляется в таблицу виртуальных функций.
При вызове виртуальной функции программа просматривает адрес таблицы виртуальных функций, хранящийся в объекте, и переходит к соответствующей таблице адресов функций. Например, если вызывается третья виртуальная функция в объявлении класса, программа запускает функцию, адрес которой хранится в третьем элементе массива.
Кратко можно сказать, что использование виртуальных функций влечет за собой следующие умеренные затраты памяти и скорости: • Каждый объект имеет свой размер, который увеличивается на значение, необходимое для хранения адреса. • Для каждого класса компилятор создает таблицу (массив) адресов виртуальных функций. • При каждом вызове функции выполняется дополнительный шаг для нахождения адреса в таблице.
Хотя невиртуальные функции более эффективны, чем виртуальные, они не предусматривают динамического связывания.
• Если в базовом классе начать объявление метода класса с ключевого слова virtual, то функция становится виртуальной для базового класса и для всех классов, производных от данного, включая вложенные производные классы.
• Если виртуальный метод вызывает через ссылку или указатель на объект, то программа использует метод, определенный для типа объекта, а не для типа указателя или ссылки. Этот процесс называется динамическим, или поздним, связыванием.
• Если вы определяете класс, который будет использоваться в качестве базового для наследования, то вы должны объявить виртуальными те методы класса, которые могут быть переопределены в производных классах.
Конструкторы не могут быть виртуальными. Создание объекта производного класса актиизирует конструктор производного, а не базового класса. Конструктор производного класса затем использует конструктор базового класса, однако, эта последовательность явно вытекает из механизма наследования. Таким образом, производный класс не наследует конструкторы базового класса, поэтому нет смысла делать их виртуальными.
Деструкторы должны быть виртуальными, за исключением тех классов, которые не будут использоваться в качестве базовых. Например, предположим, что Employee – это базовый класс, а Singer – производный класс, добавляющий член char *, который указывает на память, выделенную операцией new. Затем, когда объект Singer завершает свою работу, необходимо вызвать деструктор Singer () для того, чтобы освободить эту память.
Employee *ре = new Singer; delete ре;
Если применяется стандартное статическое связывание, то операция delete активизирует деструктор Employee(). При этом освобождается память, на которую ссылаются компоненты Employee объекта Singer, но не память, на которую указывают новые члены класса. При этом если деструкторы виртуальные, то тот же самый код активизирует деструктор Singer(), освобождающий память, на которую указывает компонент Singer, а затем вызывает деструктор Employee() для освобождения памяти, на которую указывает компонент Employee. Даже если базовый класс не требует явного деструктора, вы должны предусмотреть виртуальный деструктор, даже если он ничего не будет делать: virtual BaseClass() { }
Друзья не могут быть виртуальными функциями, поскольку они не являются членами класса, а виртуальными функциями могут быть только члены. Если это вызывает проблему при разработке, то вы можете обойти ее за счет использования виртуальных методов внутри дружественных функций.
Если в производном классе не происходит переопределение функции (виртуальной или нет), то класс будет использовать версию функции базового класса. Если класс является частью длинной цепочки порождений, то будет использоваться самая последняя версия функции. Исключение составляет случай, когда базовая версия скрыта, как описано ниже.
Переопределение скрывает методы
Предположим, что вы создали нечто наподобие:
class Dwelling { public: virtual void showperks (int а) const; }; class Hovel: public Dwelling public: virtual void showperks() const; };
Это вызывает проблему. Вы можете получить предупреждение компилятора вроде следующего: Hovel::showperks (void) hides Dwelling:: showperks (int) Hovel trurnp; trurnp.showperks(); //true trurnp.showperks(S); //false
Новое определение создает функцию showperks(), которая не принимает аргументов. Вместо того чтобы получить результат в виде двух перегруженных версий функции, это переопределение скрывает версию базового класса, в которой был аргумент int. Другими словами, переопределение унаследованных методов не является разновидностью перегрузки. Если вы переопределяете функцию в производнам классе, то при этом происходит не просто перегрузка объявления базового класса с той же самой сигнатурой функции. Вместо этого скрываются все методы базового класса с тем же именем вне зависимости от сигнатур аргументов. Этот факт приводит к нескольким практическим правилам. Во-первых, если вы переопределяете унаследованный метод, необходимо убедиться в точном совпадении с исходным прототипом. Одно сравнительно новое исключение из этого правила состоит в том, что возвращаемый тип (указатель или ссылка на базовый класс) может быть заменен указателем или ссылкой на производный класс. Это свойство назыется изменчивостью возвращаемого типа, поскольку возвращаемый тип допускается изменять параллельно с типом класса. Во-вторых, если объявление класса перегружается, вам необходимо переопределить все версии функции базового класса в производном классе. Если вы переопределяете только одну версию, то две остальных становятся скрытыми и не могут использоваться объектами производиого класса. Обратите внимание, что если не нужны никакие изменения, то переопределение может просто вызывать версию базового класса. Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.005 сек.) |