Порядок вызова конструкторов при наследовании. Ограничения наследования. Свойства указателя (ссылки) на базовый класс
Данная тема есть продолжением темы:
- Наследование. Общие понятия. Использование модификаторов private, protected, public при наследовании
Содержание
- 1. Вызов конструкторов класса при наследовании
- 2. Пример, демонстрирующий порядок вызова конструкторов. Рассматриваются 3 класса
- 3. Передача аргументов в базовый класс при вызове конструкторов
- 4. Пример, демонстрирующий передачу аргументов в конструктор базового класса
- 5. Элементы класса, которые не могут быть унаследованы
- 6. Особенности использования указателя на базовый класс
- 7. Пример, демонстрирующий использование указателя на базовый класс
- Связанные темы
Поиск на других ресурсах:
1. Вызов конструкторов класса при наследовании
Если два класса образуют иерархию наследования, то при создании экземпляра производного класса сначала вызывается конструктор базового класса конструирующий объект производного класса. Затем этот конструктор становится недоступен и дополняется кодом конструктора производного класса.
Таким образом, сначала происходит инициализация данных базового класса, затем инициализация данных производного класса.
Если классы образуют иерархию, деструкторы этих классов вызываются в обратном порядке по отношению к вызову конструкторов. Сначала вызывается деструктор производного класса, затем вызывается деструктор базового класса.
На рисунке 1 изображен порядок вызова конструкторов для двух классов A, B образующих иерархию наследования для случая создания экземпляра производного класса B.
Рисунок 1. Порядок вызова конструкторов для случая двух классов: 1 – конструктор класса A; 2 – конструктор класса B; 3 – деструктор класса B; 4 – деструктор класса A
⇑
2. Пример, демонстрирующий порядок вызова конструкторов. Рассматриваются 3 класса
В примере объявляется 3 класса A, B, C, которые образуют иерархию наследования. В классах содержится по одному конструктору и деструктору. С целью визуализации код конструкторов и деструкторов содержит вывод соответствующей информации.
#include <iostream> using namespace std; // Базовый класс class A { public: // Конструктор класса A A() { cout << "Constructor A::A()" << endl; } // Деструктор класса A ~A() { cout << "Destructor A::~A()" << endl; } }; // Производные классы class B : public A { public: // Конструктор B() { cout << "Constructor B::B()" << endl; } // Деструктор ~B() { cout << "Destructor B::~B()" << endl; } }; class C :public B { public: // Конструктор C() { cout << "Constructor C::C()" << endl; } // Деструктор ~C() { cout << "Destructor C::~C()" << endl; } }; void main() { // Создать экземпляр класса C C obj; // A() => B() => C() } // ~C() => ~B() => ~A()
Результат выполнения программы
Constructor A::A() Constructor B::B() Constructor C::C() Destructor C::~C() Destructor B::~B() Destructor A::~A()
На основе полученного результата можно сделать следующие выводы:
- конструкторы вызываются в порядке от базового класса A до унаследованного класса C, находящегося на самом нижнем уровне иерархии;
- деструкторы вызываются в обратном порядке по отношению к вызову конструкторов.
⇑
3. Передача аргументов в базовый класс при вызове конструкторов
Если в базовом классе имеется конструктор, получающий параметры, то при создании экземпляра производного класса нужно передать соответствующие аргументы в конструктор базового класса. То есть, нужно инициализировать данные базового класса в конструкторе производного класса.
Такая инициализация осуществляется с помощью специального синтаксиса, имеющего следующий вид:
// Базовый класс class Base { // Конструктор базового класса, получающий параметры Base(params_Base) { // ... } } // Производный класс class Derived : Base { // Конструктор производного класса - вызывает конструктор базового класса Derived(params_Derived) : Base(args_Base) { // ... } }
здесь
- Base, Derived – соответственно базовый и производный классы;
- params_Base – параметры, которые получает конструктор базового класса;
- arg_Base – аргументы, передаваемые в конструктор базового класса;
- params_Derived – параметры, получаемые конструктором производного класса.
Если в базовом классе Base вообще нет конструктора или есть только один конструктор без параметров Base() (конструктор по умолчанию), то не нужно обращаться к конструктору базового класса Base из конструктора производного класса Derived.
⇑
4. Пример, демонстрирующий передачу аргументов в конструктор базового класса
В примере объявляются два класса:
- Point – базовый класс, содержащий один параметризированный конструктор. Конструктор получает 2 параметра;
- PointColor – класс, унаследованный от класса Point и расширяющий класс Point свойством color (цвет точки). Конструктор класса PointColor вызывает конструктор базового класса Point с помощью специального синтаксиса языка C++.
#include <iostream> using namespace std; // Класс, описывающий точку class Point { private: double x, y; // координаты точки public: // Конструктор, получающий 2 параметра Point(double _x, double _y) : x(_x), y(_y) { } // Методы доступа double X() { return x; } double Y() { return y; } }; // Класс, наследующий класс Point, // этот класс добавляет цвет в класс Point class PointColor : public Point { private: int color; // дополнительное поле - цвет public: // Конструктор, получающий 3 параметра, // этот конструктор вызывает конструктор базового класса Point(_x, _y) PointColor(double _x, double _y, int _color) : Point(_x, _y) { color = _color; } // Метод доступа int Color() { return color; } }; void main() { // Создать экземпляр класса PointColor. // Вызываются конструкторы: Point(5, 8) => PointColor(2) PointColor pc(5, 8, 2); cout << "pc.x = " << pc.X() << endl; cout << "pc.y = " << pc.Y() << endl; cout << "pc.color = " << pc.Color() << endl; }
В вышеприведенном коде, в классе PointColor конструктор этого класса вызывает конструктор базового класса Point, передавая ему два аргумента (координаты x, y).
// Конструктор, получающий 3 параметра, // этот конструктор вызвает конструктор базового класса Point(_x, _y) PointColor(double _x, double _y, int _color) : Point(_x, _y) { color = _color; }
Третий параметр _color инициализирует внутреннюю переменную color.
После запуска программа выдаст следующий результат
pc.x = 5 pc.y = 8 pc.color = 2
⇑
5. Элементы класса, которые не могут быть унаследованы
В C++ не все элементы класса можно наследовать, поскольку они несовместимы с самой идеей наследования. К числу этих элементов можно отнести:
- конструкторы (все виды конструкторов);
- деструктор;
- перегруженные операторы new;
- перегруженные операторы присваивания (=);
- дружественные функции или отношения дружественности. Это означает, что если базовый класс имеет дружественные функции, то эти функции не являются дружественными в унаследованном классе.
⇑
6. Особенности использования указателя на базовый класс
Если классы образуют иерархию наследования, то для этих классов справедливо следующее правило:
- указатель (ссылка) на базовый класс может указывать на экземпляр этого класса и экземпляр любого производного класса в иерархии наследования. Или иными словами, указатель (ссылка) на базовый класс может быть приведен к типу любого производного класса в иерархии наследования.
Благодаря этому правилу обеспечивается выполнение механизма полиморфизма. Более подробно о полиморфизме можно прочитать здесь.
На рисунке 2 представлен пример иерархии и возможные присваивания указателю значений адресов экземпляров производных классов
Рисунок 2. Указатель p на базовый класс A может указывать на экземпляры производных классов, унаследованные от базового класса A
⇑
7. Пример, демонстрирующий использование указателя на базовый класс
В примере объявляются два класса Base и Derived. Классы образуют иерархию наследования. Класс Derived наследует класс Base.
// Базовый класс class Base { // ... }; // Производный (унаследованный) класс // здесь ключевое слово public - обязательное class Derived : public Base { // ... }; void main() { // 1. Объявить указатель на базовый класс Base* p1; // 2. Объявить экземпляры базового и производного класса Base obj1; Derived obj2; // 3. Присвоить указателю адреса экземпляров p1 = &obj1; // можно p1 = &obj2; // можно, теперь p указывает на экземпляр производного класса // 4. Так делать нельзя Derived* p2; // Указатель на производный класс p2 = &obj2; // так можно, типы совпадают: Derived* <= Derived* p2 = &obj1; // ошибка компиляции, базовый класс не может разширяться // до возможностей производного класса Derived* <= Base* }
В приведенном выше примере в функции main() объявляется указатель на базовый класс Base и два экземпляра (объекта) классов Base и Derived с именами соответственно obj1 и obj2. Затем поочередно происходит присваивание указателю p значения адресов экземпляров obj1 и obj2. Присваивание происходит успешно.
На следующем шаге с целью демонстрации объявляется указатель p2 на производный класс Derived. Этот указатель поочередно принимает значение адресов экземпляров obj2 и obj1. В случае с экземпляром obj2 присваивание выполняется корректно
Derived* p2; p2 = &obj2; // здесь все работает, типы совпадают
В случае присваивания адреса экземпляра obj1
p2 = &obj1;
компилятор выдает ошибку. Это логично, поскольку экземпляр obj1 базового класса Base не может быть расширен до возможностей производного класса Derived, потому что он не наследует этот класс (класс Base не наследует класс Derived).
⇑
Связанные темы
- Наследование. Общие понятия. Использование модификаторов private, protected, public при наследовании
- Полиморфизм. Виртуальные функции. Общие понятия. Спецификаторы virtual, override. Примеры
- Абстрактный класс. Чисто виртуальная функция. Примеры
⇑