Реализация механизма виртуальных функций с помощью таблицы виртуальных функций.

Рассмотрим простой код:

classBase

{

public:

virtualvoidvf ()

{

cout<< "Базовый класс\n";

}

};

class Derived : public Base

{

public:

voidvf () // это тоже виртуальная функция

{

cout<< "Производный класс\n";

}

};

Функции Base::vf и Derived::vf являются виртуальными. Об этом говорит ключевое слово virtual в базовом классе. А производный класс наследует это свойство для своего метода.

Для виртуальных методов память выделяется точно так же, как и для обычных: на этапе компиляции под эти методы выделяются участки памяти, первые адреса которых являются адресами методов. Но так как методы виртуальные, то фактические адреса метода не привязывается к именам: Base::vf и Derived::vf. Адрес метода, который назначается на этапе компиляции при выделении памяти, будем называть настоящим (или фактическим) адресом.

Когда в базовом классе объявляется хотя бы одна виртуальная функция, то для всех полиморфных классов создаётся таблица виртуальных функций (virtualfunctiontable).

Встречаются разные названия этой таблицы: virtualfunctiontable, virtualmethodtable, vtable, vftable.

Таблица виртуальных функций - это одномерный массив указателей на функции. Количество элементов в массиве равно количеству виртуальных функций в классе.

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

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

Помимо создания виртуальной таблицы функций, в базовом классе объявляется поле __vfptr - указатель на vtable. Конечно же, этот указатель наследуется всеми производными классами. __vfptr можно увидеть при отладке.

__vfptr объекта указывает на vtable класса, которому принадлежит объект.

Рассмотрим пример. Допустим, в базовом классе определено две функции: f - не виртуальная и vf - виртуальная:

Base* object = new Derived;

object->f();

В данном случае компилятор не обращает внимания, объект какого типа на самом деле хранится в object. Компилятор смотрит на тип укзаталя и вызывает соответствующий метод - Base::f().

Base* object = new Derived;

object->vf();

В данном случае процессор видит, что vf - виртуальный метод. Поэтому он ищет в таблице виртуальных функций нужную запись. Но адрес таблицы виртуальных функций он узнаёт через __vfptr, а этот указатель указывает на таблицу своего класса. Соответственно, будет вызван метод того класса, чей объект вызывает метод vf.

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

Определение шаблона класса

Шаблон класса

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

Общая форма объявления параметризованного класса:

template<classтип_данных>classимя_класса { . . . };

Основные свойства шаблонов классов

· Компонентные функции параметризованного класса автоматически являются параметризованными. Их не обязательно объявлять как параметризованные с помощью template.

· Дружественные функции, которые описываются в параметризованном классе, не являются автоматически параметризованными функциями, т.е. по умолчанию такие функции являются дружественными для всех классов, которые организуются по данному шаблону.

· Если friend-функция содержит в своем описании параметр типа параметризованного класса, то для каждого созданного по данному шаблону класса имеется собственнаяfriend-функция.

· В рамках параметризованного класса нельзя определить friend-шаблоны (дружественные параметризованные классы).

· С одной стороны, шаблоны могут быть производными (наследоваться) как от шаблонов, так и от обычных классов, с другой стороны, они могут использоваться в качестве базовых для других шаблонов или классов.

· Шаблоны функций, которые являются членами классов, нельзя описывать как virtual.

· Локальные классы не могут содержать шаблоны в качестве своих элементов.

Понятие абстрактного класса

Класс в котором определен хотя бы один чисто виртуальный метод называется абстрактным. Нельзя создавать объекты абстрактного класса. Абстрактный класс может использоваться только в качестве базового класса для построения других классов.

Класс, порожденный от абстрактного класса, должен переопределять описанные в нем чисто виртуальные методы. В противном случае этот класс также будет абстрактным.

В качестве примера абстрактного класса мы приведем класс Abstract, в котором описан чисто виртуальный метод PureFunc. Обратите внимание, что этот метод не определен в классе Abstract. Определение метода содержится только в порожденном классе Fact.

// Абстрактный класс Abstract

classAbstract

{

public:

// Чисто виртуальный метод, не имеет определения

virtualintPureFunc(void) = 0;

voidSetValue(inti) {iValue = i;}

intiValue;

};

// Класс Fact

class Fact : public Abstract

{

intPureFunc(void) {return iValue * iValue;}

};

Принципы ООП

Основные принципы ООП

Объектно-ориентированное программирование основано на «трех китах» - трех важнейших принципах, придающих объектам новые свойства. Этими принципами являются инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Инкапсуляция есть объединение в единое целое данных и алгоритмов обработки этих данных. В рамках ООП данные называются полями объекта, а алгоритмы - объектными методами.

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

Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую. Можно сказать, что ООП «провоцирует» разработку библиотек объектов, таких как TurboVision.

Наследование

Наследование есть свойство объектов порождать своих потомков. Объект-потомок автоматически наследует от родителя все поля и методы, может дополнять объекты новыми полями и заменять (перекрывать) методы родителя или дополнять их.

Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые «умеют» делать то, что не реализовано в родителе.

Последовательное проведение в жизнь принципа «наследуй и изменяй» хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход.

Полиморфизм

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

Наши рекомендации