
- •Примеры
- •Примеры
- •[Править]в объектно-ориентированных языках
- •Параметрический полиморфизм
- •[Править]Специальный полиморфизм
- •[Править]Неявная типизация
- •Статический и динамический полиморфизм
- •Полиморфизм включения
- •Параметрический полиморфизм
- •Полиморфизм переопределения
- •Полиморфизм-перегрузка
- •Сравнение полиморфизма в функциональном и объектно-ориентированном программировании
- •Определение методов класса вне класса
- •Второй пример
- •Что вам необходимо знать
- •25. Inline Функции
- •26. Указатель this
- •27. Селекторы
- •5.1 Сопоставление шаблонов
- •5.2 Синтаксис селекторов
- •5.2.1 Группировка
- •5.3 Универсальный селектор
- •5.4 Селекторы типов
- •5.5 Селекторы потомков
- •5.6 Селекторы дочерних элементов
- •5.7 Селекторы сестринских элементов
- •5.8 Селекторы атрибутов
- •5.8.1 Сопоставление атрибутам и значениям атрибутов
- •5.8.2 Значения атрибутов, используемые в dtd по умолчанию
- •5.8.3 Селекторы классов
- •28. Модификаторы
- •29. Конструкторы
- •Назначение конструктора
- •Виды конструкторов
- •Конструктор по умолчанию
- •Конструктор копирования
- •Конструктор преобразования
- •Виртуальный конструктор
- •Синтаксис
- •Пример Конструктора по умолчания
- •31. Деструкторы
- •Виртуальный деструктор
- •Создание простого конструктора
- •Конструкторы и параметры по умолчанию
- •Перегрузка конструкторов
- •Представление о деструкторе
- •Что вам необходимо знать
- •32. Друзья
- •Определение друзей класса
- •Ограничение количества друзей
- •Что вам необходимо знать
- •Совместное использование элемента данных
- •Использование элементов с атрибутами public static, еслиобъекты не существуют
- •Использование статических функций-элементов
- •Что вам необходимо знать
- •37. Перезагрузка операций
- •Перегрузка унарных операций
- •Перегрузка постфиксных операций
- •Перегрузка бинарных операций
- •38. Преобразования типов
- •Const_cast не применим:
- •39. Наследование
- •Простое наследование
- •Второй пример
- •Что такое защищенные элементы
- •Разрешение конфликта имен
- •Что вам необходимо знать
- •40. Виртуальные функции и полиморфизм Виртуальные функции
- •Перекрытие методов
- •Абстрактные классы и чистые виртуальные функции
- •Модификаторы доступа
- •Ковариантность
Абстрактные классы и чистые виртуальные функции
Расширим пример текстового файла. Предположим, что нам нужно сделать для класса TextFile базовый класс File, от которого будет унаследован еще один класс RTFFile. Однако, в такой ситуации неизвестно как реализовать метод read()класса File, т.к. класс File не реализует поведение какого-то конкретного типа файлов, а представляет интерфейс для работы с различными файлами. В этом случае, метод read(...) этого класса нужно сделать чистым виртуальным, дописав "= 0" после его сигнатуры:
struct File { virtual string read(size_t count) = 0; };
Это означает, что метод read(...) должен быть определен в классах наследниках. Теперь класс File стал абстрактным, и его экземпляры невозможно создать. Но зато можно работать через указатель на абстрактный класс с объектами производных классов, например, так:
File *f = new TextFile("text.txt"); //различные действия с файлом text.txt delete f; f = new RTFFile("rich_text.rtf"); //различные действия с файлом rich_text.rtf delete f;
Следует отметить, что в любой иерархии классов деструктор всегда должен быть виртуальным. Рассмотрим пример, поясняющий важность этого факта:
struct Person { public: ~Person() {} private: string name; //... }; struct Student : Person { public: Student() { someData = new Data(); } ~Student() { delete someData; } //... private: Data *someData; }; //... Student *s = new Student(); //... delete s; //вызовется деструктор класса Student, память по указателю someData освободится Person *p = new Student(); //... delete p; /*вызовется деструктор класса Person, а не Student, т.к. он не является виртуальным, несмотря на то, что на самом деле объект - экземпляр Student. В этом случае произойдет утечка памяти, т.к. память по указателю someData не освободится */
Деструктор можно также сделать чистым виртуальным, но при этом его тело нужно определить снаружи класса.
Таблица виртуальных функций (Virtual Function Table)
Для каждого класса, содержащего виртуальные методы, или унаследованного от класса с виртуальными методами, создается таблица виртуальных функций. Эта таблица предназначена для вызова нужных реализаций виртуальных методов во время исполнения программы. При создании экземпляра класса, указатель на VFT этого класса помещается в самое начало созданного объекта.
Как известно, конструирование объекта происходит поэтапно и начинается созданием объекта самого первого класса в иерархии наследования. Во время этого процесса перед вызовом конструктора каждого класса указатель на VFT устанавливается равным указателю на VFT текущего конструируемого класса. Например, у нас есть 3 класса: A, B, C (Bнаследуется от A, C наследуется от B). При создании экземпляра С, произойдут 3 последовательных вызова конструкторов: сначала A(), затем B(), и в конце C(). Перед вызовом конструктора A() указатель на VFT будет указывать на таблицу класса A, перед вызовом B() он станет указывать на таблицу класса B() и т.д. Аналогичная ситуация при вызове деструкторов, только указатель будет менятся от таблицы самого младшего класса к самому старшему.
Из этого факта следует правило: в конструкторах и деструкторах нельзя вызывать виртуальные методы. Посмотрим, что произойдет, если нарушить это правило:
struct A { A() { f(); } virtual void f() { cout << "A::f()" << endl; } }; struct B : A { B() { f(); } virtual void f() { cout << "B::f()" << endl; } }; struct C : B { C() { f(); } virtual void f() { cout << "C::f()" << endl; } }; //... C c; //создание объекта класса C
В этом примере на экран будет выведено:
A::f()
B::f()
C::f()