Возможность вызова виртуальных функций производных классов через указатель или ссылку на базовый класс называетсяся полиморфизмом подтипов, или включающим полиморфизмом.
Класс, который содержит или наследует виртуальную функцию, называется полиморфным классом.
Термин позднее / отложенное / динамическое связывание относится к тому, что компилятор не может определить, какая виртуальная функция должна вызываться, если к ней обращаться через указатель или ссылку на базовый класс. Эта особенность виртуальных функций приводит к тому, что адрес нужной вызываемой функции может быть определен только во время исполнения программы. Позднее связывание реализовано следующим образом:
• для каждого полиморфного класса компилятор строит таблицу адресов виртуальных функций (vtable)
• каждый объект полиморфного класса содержит скрытый указатель
(vptr ) на таблицу адресов виртуальных функций
• компилятор автоматически вставляет в начачало конструктора полиморфного класса фрагмент кода, который инициализирует vptr
• при вызове виртуальной функции ее адрес извлекается из таблицы
vtable, на которую указывает vptr объекта
int main()
{
Base * pBase = new Inherited;
pBase->foo();
pBase->bar();
pBase->baz();
//pBase->qux(); // Ошибка
delete pBase;
return EXIT_SUCCESS;
}
Что происходит при запуске программы? Вначале объявляем указатель на объект типа Base, которому присваиваем адрес вновь созданного объекта типа Inherited. При этом вызывается конструктор Base, инициализирует VPTR адресом VTABLE класса Base, а затем конструктор Inherited, который перезаписывает значение VPTR адресом VTABLE класса Inherited. При вызове pBase->foo(), pBase->bar() и pBase->baz() компилятор через указатель VPTR достает фактический адрес тела функции из таблицы виртуальных функций. Как это происходит? Вне зависимости от конкретного типа объекта компилятор знает, что адрес функции foo() находится на первом месте, bar() — на втором, и т.д. (как я и говорил, в порядке объявления функций). Таким образом, для вызова, к примеру, функции baz() он получает адрес функции в виде VPTR+2 — смещение от начала таблицы виртуальных функций, сохраняет этот адрес и подставляет в команду call. По этой же причине, вызов pBase->qux() приводит к ошибке: несмотря на то, что фактический тип объекта Inherited, когда мы присваиваем его адрес указателю на Base, происходит восходящее приведение типа, а в таблице VTABLE класса Base никакого четвертого метода нет, поэтому VPTR+3 указывало бы на «чужую» память (к счастью, такой код даже не компилируется).
Механизм виртуальных функций не поддерживает передачу аргументов по умолчанию в такие функции: Если функция производного класса вызывается через указатель или ссылку на базовый класс, то ей передаются аргументы по умолчанию, указанные в базовом классе, т.к. они определяются на этапе компиляции.
Если класс являлся полиморфным, то его деструктор должен быть виртуальным и всегда иметь реализацию, т.к. может быть вызван из деструктора производного класса.
Виртуальная функция называется чистой, если она определена только как спецификатор интерфейса, т. е. не имеет реализации:
virtual тип имя_функц (спараметры) = 0;
Предполагается, что чисто виртуальная функция определяется в производных классах.
Класс, который содержит хотя бы одну чисто виртуальную функцию, называется абстрактным классом. Такой класс может использоваться только как базовый для других. Так как у виртуальных функций нет реализации, то нельзя создать объект абстрактного класса.
Абстрактный класс:
· Не может использоваться в качестве аргумента или типа возвращаемого функцией значения
· Нельзя использовать в явном преобразовании типов
· Можно определить указатель или ссылку на абстрактный класс, чтобы использовать абстрактный класс для описания интерфейсов базовых классов и работы с производными классами через указатели / ссылки на базовый класс. Если класс, производный от абстрактного класса, и в нем не определены все чисто виртуальные функции, то он также является абстрактным.
Т.к. деструктор абстрактного класса может быть вызван компилятором при уничтожении объекта производного класса, то деструктор абстрактного класса должен быть определен как виртуальная функция с пустым телом
override - используется в классе-потомке, чтобы указать что функция должна переопределять виртуальную функцию, объявленную в базовом классе. Это позволяет избавиться от ошибок, когда из-за опечатки вместо переопределения существующей виртуальной функции была создана новая (с другим именем или сигнатурой).
final - С++ 11 также добавляет возможность предотвращения наследования от классов или просто предотвращения переопределения методов в производных классах. Это делается с помощью специального идентификатора final. Например:
struct Base1 final { };
struct Derived1 : Base1 { }; // ill-formed because the class Base1
// has been marked final
struct Base2 {
virtual void f() final;
};
struct Derived2 : Base2 {
void f(); // ill-formed because the virtual function Base2::f has
// been marked final
};
8. C++: средства реализации состояния объектов; реализация поведения.
Состояние
Состояние объекта характеризуется перечнем (обычно статическим) всех свойств данного объекта и текущими (обычно динамическими) значениями каждого из этих свойств.
В С++ экземпляры класса называются объектами, каждый объект содержит свой набор атрибутов (данных) класса.
Переменные-члены
Переменные-члены класса могут объявляться как:
• статические (static)
Тогда будет существовать только одна копия этой переменной, независимо от того, сколько объектов класса создается.
Каждый объект просто использует (совместно с другими) эту переменную. Одна и та же статическая переменная будет использоваться всеми классами, производными от класса, в котором эта переменная создается.
Статическая переменная-член класса создается еще до того, как создан объект этого класса; по сути, это просто глобальная переменная, область видимости которой ограничена классом, в котором она объявлена.
Все стататические переменнные-члены по умолчанию инициализируются нулем, однако можно задать отличное от этого значение.
• обычные
Для обычных переменных-членов класса при создании каждого объекта создается их новая копия, и доступ к каждой копии возможен только через этот объект (т.о., для обычных переменных каждый объект обладает собственными копиями переменных).
Функции-члены
Помимо переменных-членов статическими можно объявлять и функции-члены.
Доступ к объявленной статической функции-члену класса возможен только для других статических членов этого класса (и для глобальных данных и функций).
У стат. ф.-члена нет указателя this, они не могут быть виртуальными, объявляться как const и volatile. Стат. ф.-члены класса могут вызываться любым объектом этого класса, а также через имя класса и оператор расширения области видимости без связи с объектом этого класса.
Ф.-члены класса могут объявляться постоянными (const). Если ф-ция объявлена постоянной, она не может изменить вызывающий ее объект (только поля, помеченные mutable). Кроме того, постоянный объект не может вызвать непостоянную ф-цию член.
Однако постоянная ф.-член может вызываться как постоянными, так и непостоянными объектами.
Дата: 2019-02-19, просмотров: 293.