Каждый класс — базовый и производный может иметь конструктор и деструктор. В каком порядке выполняются эти функции при простом и множественном наследовании? Рассмотрим этот вопрос на модельных примерах.
Простое наследование.
Пример 36.
Программа иллюстрирует порядок работы конструктора и деструктора по схеме Base <- Derive1 <- Derive2. Она не делает ничего, кроме создания объектов типа Derive1 и Derive2.
#include<iostream.h>
#include<conio.h>
class Base
{ public:
Base () { cout << "Конструктор базового класса Base\n"; }
~Base () { cout << "Деструктор базового класса Base\n"; }
};
class Derive1: public Base
{ public:
Derive1 () { cout << "Конструктор производного класса Derive1\n"; }
~Derive1 () { cout << "Деструктор производного класса Derive1\n"; }
};
class Derive2: public Derive1
{ public:
Derive2 () { cout << "Конструктор производного класса Derive2\n"; }
~Derive2 () { cout << "Деструктор производного класса Derive2\n"; }
};
void main ()
{ clrscr ();
Derive1 d1; cout << endl;
Derive2 d2; cout << endl;
getch();
}
Результаты программы:
Конструктор базового класса Base
Конструктор производного класса Derive1 // объект d1 создан
Конструктор базового класса Base
Конструктор производного класса Derive1
Конструктор производного класса Derive2 // объект d2 создан
Деструктор производного класса Derive2
Деструктор производного класса Derive1
Деструктор базового класса Base // объект d2 уничтожен
Деструктор производного класса Derive1
Деструктор базового класса Base // объект d1 уничтожен
Комментарии к программе:
Функции-конструкторы выполняются при каждом создании объекта, начиная с класса Base — это естественный порядок выполнения. Для создания объекта производного класса необходима инициализация базового класса, так что конструктор должен выполняться.
|
С другой стороны, деструктор в производном классе должен выполняться раньше выполнения деструктора базового класса. Если деструктор базового класса выполнится раньше, то деструктор производного класса вообще не сможет выполниться. Поэтому деструкторы вызываются в порядке, обратном вызову конструкторов.
Множественное наследование.
В языке С++ производный класс может наследовать от нескольких базовых классов. При объявлении производного класса базовые классы перечисляются через запятую. Причем при создании объекта конструкторы выполняются в порядке следования базовых классов слева направо.
Пример 37.
Программа иллюстрирует порядок работы конструктора и деструктора по схеме множественного наследования: (Base1, Base2) <- Derive. Она не делает ничего, кроме создания объектов типа Base1, Base2, Derive. Рассмотрим модифицированную программу примера 36.
#include<iostream.h>
#include<conio.h>
class Base1
{ public:
Base1 () { cout << "Конструктор базового класса Base1\n"; }
~Base1 () { cout << "Деструктор базового класса Base1\n"; }
};
class Base2
{ public:
Base2() { cout << "Конструктор производного класс Base2 \n"; }
~ Base2() { cout << "Деструктор производного класса Base2\n"; }
};
class Derive: public Base1, Base2
{ public:
Derive () { cout << "Конструктор производного класса Derive\n"; }
~Derive () { cout << "Деструктор производного класса Derive\n"; }
};
void main ()
{ clrscr ();
Base1 b1; cout << endl;
Base2 b2; cout << endl;
Derive d; cout << endl;
getch();
}
Результаты программы:
Конструктор базового класса Base1 // объект b1 создан
Конструктор базового класса Base2 // объект b2 создан
|
Конструктор базового класса Base1
Конструктор базового класса Base2
Конструктор производного класса Derive // объект d создан
Деструктор производного класса Derive
Деструктор базового класса Base2
Деструктор базового класса Base1 // объект d уничтожен
Деструктор базового класса Base2 // объект b2 уничтожен
Деструктор базового класса Base1 // объект b1 уничтожен
Результаты работы программы показывают, что деструкторы выполняются в порядке обратном по отношению к выполнению конструкторов.
Виртуальные деструкторы
Методы и деструкторы (но не конструкторы!) могут быть виртуальными. Виртуальные деструкторы обычно применяются, когда в некотором классе необходимо удалить объекты производного класса, на которые ссылаются указатели на базовый класс. Типичной является ситуация, когда динамически создается объект производного класса, а используется указатель на базовый класс.
Пример 38.
Рассмотрим простое наследование по схеме Base <- Derive1 <- Derive2. Программа (на основе примера 36) иллюстрирует порядок работы конструкторов и деструкторов в классах при динамическом выделении памяти объекту производного класса через указатель на базовый класс и уничтожение объектов с использованием виртуального деструктора. Если объявить деструктор базового класса виртуальным, то все деструкторы производных классов также являются виртуальными.
#include<iostream.h>
#include<conio.h>
class Base
{ public:
Base () { cout << "Конструктор базового класса Base\n"; }
virtual ~Base () { cout << "Деструктор базового класса Base\n"; }
|
};
class Derive1: public Base
{ public:
Derive1 () { cout << "Конструктор производного класса Derive1\n"; }
~Derive1 () { cout << "Деструктор производного класса Derive1\n"; }
};
class Derive2: public Derive1
{ public:
Derive2 () { cout << "Конструктор производного класса Derive2\n"; }
~Derive2 () { cout << "Деструктор производного класса Derive2\n"; }
};
void main ()
{ clrscr ();
Base *pb = new Derive2; // выделение памяти объекту Derive2
if (! pb)
{ cout << "Недостаточно памяти\n";
return 1; // аварийное окончание программы
}
cout << endl;
delete pb; // вызов всех деструкторов производных и базового классов
return 0;
}
Результаты программы:
Конструктор базового класса Base
Конструктор производного класса Derive1
Конструктор производного класса Derive2 // создание объекта типа Derive2
Деструктор производного класса Derive2
Деструктор производного класса Derive1
Деструктор базового класса Base // уничтожение объекта типа Derive2
Комментарии к программе:
При разрушении объекта с помощью операции delete через указатель на базовый класс были корректно вызваны деструкторы всех классов в нужном порядке. Если бы деструктор базового класса не был объявлен виртуальным, то вызывался бы только деструктор базового класса (проверьте это). Динамическая память, выделенная объекту конструктором, при разрушении объекта обычным деструктором не освобождалась бы корректно.
Пример 39.
Рассмотрим модифицированную программу на основе примеров 33 и 35, в которой используется абстрактный базовый класс Figure с чистой виртуальной функцией show_area() и виртуальным деструктором. Базовый класс Figure описывает плоскую фигуру с двумя измерениями, которые задаются конструктором со вторым параметром по умолчанию. Нельзя создать объект данного класса, но можно определить указатель на тип класса. От этого класса на основе расширяющегося наследованиия строятся производные классы по схеме: Figure <- (Triangle, Rectangle, Circle). В производных классах определены свои функции show_area().
С помощью указателя на базовый класс можно сослаться на объекты производных классов и вывести данные о них на экран в процессе исполнения программы. Виртуальный деструктор последовательно уничтожает объекты в порядке обратном тому, в котором они создавались конструктором.
#include<iostream.h>
#include<conio.h>
class Figure // абстрактный базовый класс
{ protected: // защищенные элементы, доступные в производных классах
double x, y;
public:
Figure (double i, double j=0) // конструктор с параметром по умолчанию
{ x = i, y = j; // задание измерений фигур
cout<<"Конструктор базового класса Figure\n";
}
virtual void show_area() = 0; // чистая виртуальная функция
virtual ~Figure () // виртуальный деструктор Figure { cout<<"Деструктор базового класса Figure\n";}
};
class Triangle: public Figure // производный класс Triangle
{ public:
Triangle (double i, double j): Figure (i, j) // конструктор Triangle
{ cout<<"Конструктор Triangle\n"; }
void show_area() // виртуальная функция Triangle
{ cout<<"Треугольник с высотой "<< x <<" и основанием "<< y;
cout<<" имеет площадь = "<< x*0.5*y <<endl;
}
~Triangle() // виртуальный деструктор Triangle
{ cout<<"Деструктор классаTriangle \n";}
};
class Rectangle: public Figure // производный класс Rectangle
{ public:
Rectangle (double i, double j): Figure (i, j) // конструктор Rectangle
{ cout<<"Конструктор Rectangle\n"; }
void show_area() // виртуальная функция Rectangle
{ cout<<"Прямогольник со сторонами "<< x <<" и "<< y;
cout<<" имеет площадь = "<< x*y <<endl;
}
~Rectangle() // виртуальный деструктор Rectangle { cout<<"Деструктор класса Rectangle\n"; }
};
class Circle: public Figure // производный класс Circle
{ public:
Circle (double i): Figure (i) // конструктор Circle {cout<<"Конструктор Circle\n"; }
void show_area() // виртуальная функция Circle
{ cout<<"Круг с радиусом "<< x;
cout << " имеет площадь = " << 3.14*x*x << endl;
}
~Circle() // виртуальный деструктор Circle { cout<<"Деструктор класса Circle\n"; }
};
void main ()
{ clrscr();
cout << "Работа программы:\n";
Figure *p; // объявление указателя класса Figure
Triangle t (3,4); // создание объекта класса Triangle
Rectangle r (5,6); // создание объекта класса Rectangle
Circle c (2); // создание объекта класса Circle
p = &t; // базовый указатель на объект типа Triangle
p -> show_area(); // вывод сообщения о площади объекта типа Triangle
p = &r; // базовый указатель на объект типа Rectangle
p -> show_area(); // вывод сообщения о площади объекта типа Rectangle
p = &c; // базовый указатель на объект типа Circle
p -> show_area(); // вывод сообщения о площади объекта типа Circle
}
Результаты программы:
Работа программы:
Конструктор базового класса Figure
Конструктор Triangle
Конструктор базового класса Figure
Конструктор Rectangle
Конструктор базового класса Figure
Конструктор Circle
Треугольник с высотой 3 и основанием 4 имеет площадь = 6
Прямогольник со сторонами 5 и 6 имеет площадь = 30
Круг с радиусом 2 имеет площадь = 12.56
Деструктор класса Circle
Деструктор базового класса Figure
Деструктор класса Rectangle
Деструктор базового класса Figure
Деструктор классаTriangle
Деструктор базового класса Figure