Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ
Сравнение типов наследования
Порядок вызова конструкторов В С++ при конструировании экземпляра-наследника всегда происходит предварительный вызов конструктора базового класса В С++ вызов конструктора базового класса происходит до инициализации полей класса наследника Конструктор класса-наследника может явно передать конструктору базового класса необходимые параметры при помощи списка инициализации Если вызов конструктора родительского класса не указан в списке инициализации, компилятор пытается вызвать конструктор по умолчанию класса-родителя
Пример class CEmployee {public: std::string GetName()const {return m_name;} protected: CEmployee(std::string const& name) : m_name(name) {std :: cout
Пример class CEmployee{ public: string GetName()const {return m_name;} protected: CEmployee(string const& name): m_name(name) {cout
Порядок вызова деструкторов В С++ порядок вызова деструкторов ВСЕГДА обратен порядку вызова конструкторов Сначала вызывается деструктор класса- наследника, затем деструктор базового класса и т.д. вверх по иерархии классов
Пример class CTable{ public: CTable(string const& dbFileName) { m_tableFile.Open(dbFileName); cout
ПЕРЕГРУЗКА МЕТОДОВ В КЛАССЕ- НАСЛЕДНИКЕ
Перегрузка методов в классе-наследнике В С++ метод производного класса замещает собой ВСЕ МЕТОДЫ родительского класса С ТЕМ ЖЕ ИМЕНЕМ Количество и типы аргументов значения не имеют Для вызова метода родительского класса из метода класса наследника используется метод Base::
Пример class CBase{ public: void Print ( ) { cout
Виртуальные функции
Задача – иерархия геометрических фигур Рассмотрим следующую иерархию геометрических фигур: CShape – базовый класс «фигура» CCircle – класс, моделирующий окружность CRectangle – класс, моделирующий прямоугольник Каждая фигура обладает следующими свойствами: Имя: «Shape», «Circle», «Rectangle» Площадь фигуры
Решение без виртуальных функций class CShape{ public: string GetType() const {return Shape;} double GetArea() const {return 0;} }; class CRectangle : public CShape {public: CRectangle (double width, double height) : m_width(width), m_height(height) {} string GetType () const {return Rectangle;} double GetArea()const {return m_width*m_height;} private: double m_height; double m_width; }; class CCircle : public CShape {public: CCircle (double radius) : m_radius(radius) { } string GetType ( ) const {return Circle;} double GetArea( ) const {return 3.14*m_radius*m_radius;} private: double m_radius;};
Так, вроде, все работает int main ( ) { CCircle circle(10); CRectangle rectangle(20,10); cout
А вот так - нет void PrintShapeArea (CShape const& shape) { cout
Проблема Проблема в том, что в данной ситуации, при выборе вызываемых методов компилятор руководствуется типом ссылки или указателя В нашем случае происходит вызов метода класса CShape, так как функция PrintShapeArea принимает ссылку данного типа Методы, при вызове которых необходимо руководствоваться типом объекта, должны быть объявлены виртуальными
Виртуальные методы Метод класса может быть объявлен виртуальным, если допускается его альтернативная реализация в порожденном классе При вызове виртуальной функции через указатель или ссылку на объект базового класса будет вызвана реализация данной функции, специфичная для фактического типа объекта Виртуальные функции обозначаются в объявлении класса при помощи ключевого слова virtual Виртуальные функции позволяют использовать полиморфизм Полиморфизм позволяет осуществлять работу с разными реализациями через один и тат же интерфейс
Решение с виртуальными функциями class CShape{ public: virtual string GetType() const {return Shape;} virtual double GetArea() const {return 0;} }; class CRectangle : public CShape {public: CRectangle (double width, double height) : m_width(width), m_height(height) {} virtual string GetType () const {return Rectangle;} virtual double GetArea()const {return m_width*m_height;} private: double m_height; double m_width; }; class CCircle : public CShape {public: CCircle (double radius) : m_radius(radius) { } virtual string GetType ( ) const {return Circle;} virtual double GetArea( ) const {return 3.14*m_radius*m_radius;} private: double m_radius;};
Теперь работает правильно void PrintShapeArea (CShape const& shape) { cout
Особенности реализации виртуальных функций в С++ В С++ функции, объявленные в базовом классе виртуальными, остаются виртуальными в классе-потомке Использовать слово virtual в классах-наследниках не обязательно (хотя и желательно) В С++ виртуальные функции не являются виртуальными, если они вызваны в конструкторе или деструкторе данного класса Такое поведение специфично для механизма инициализации и разрушения объектов в С++; в других языках программирования может быть и по- другому
class CShape{ public: virtual string GetType() const {return Shape;} virtual double GetArea() const {return 0;} }; class CRectangle : public CShape {public: CRectangle (double width, double height) : m_width(width), m_height(height) {} virtual string GetType () const {return Rectangle;} virtual double GetArea()const {return m_width*m_height;} private: double m_height; double m_width; }; class CCircle : public CShape {public: CCircle (double radius) : m_radius(radius) { } virtual string GetType ( ) const {return Circle;} virtual double GetArea( ) const {return 3.14*m_radius*m_radius;} private: double m_radius;};
Виртуальный деструктор Деструктор класса, имеющего наследников, всегда должен явно объявляться виртуальным Это обеспечивает корректный вызов деструктора нужного класса при вызове оператора delete с указателем на базовый класс Деструктор, не объявленный явно виртуальным, а также автоматически сгенерированный деструктор является не виртуальным Классы без виртуальных деструкторов не предназначены для расширения Классы стандартных коллекций STL (строки, векторы) не имеют виртуальных деструкторов, поэтому наследоваться от них нельзя
Проблемы при использовании невиртуального деструктора class CBase{ public: CBase ( ) : m_pBaseData (new char [100]) { cout
OUTPUT: Base class data were created Derived class data were created Derived class data were deleted Base class data were deleted Base class data were created Derived class data were created Derived class data were deleted Base class data were deleted Base class data were created Derived class data were created Base class data were deleted Проблемы при использовании невиртуального деструктора
Исправляем проблему, объявив деструктор виртуальным class CBase{ public: CBase ( ) : m_pBaseData (new char [100]) { cout
OUTPUT: Base class data were created Derived class data were created Derived class data were deleted Base class data were deleted Base class data were created Derived class data were created Derived class data were deleted Base class data were deleted Base class data were created Derived class data were created Derived class data were deleted Base class data were deleted Исправляем проблему, объявив деструктор виртуальным
ИТОГИ Всегда используем виртуалбный деструктор: В базовых классах В классах, от которых возможно наследование в будущем Например, в классах с виртуальными методами Не используем виртуальные деструкторы: В классах, от которых не планируется создавать производные классы в будущем Также возможно в базовом классе объявить защищенный невиртуальный деструктор Объекты данного напрямую удалить невозможно – только через указатель на класс-наследник Данный деструктор будет доступен классам- наследникам