Как сделать производный класс

Обновлено: 05.07.2024

Друзья — это функции или классы, объявленные с использованием ключевого слова friend.

Функция, не являющаяся членом класса, может получить доступ к закрытым и защищенным членам класса, если они объявлены друзьями этого класса. Это осуществляется путем включения объявления этой внешней функции в класс, предваряя его ключевым словом friend.

Функция duplicate является дружественной классу Rectangle. Таким образом, функция duplicate имеет доступ к членам width и height (являющимися закрытыми) различных объектов типа Rectangle. Однако, ни в объявлении duplicate, ни в ее последующем использовании в main(), функция duplicate не считается членом класса Rectangle. Это не так. Она просто имеет доступ к закрытым и защищенным членам класса, не являясь членом класса.

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

Дружественные классы

Подобно дружественным функциям, дружественный класс — это класс, члены которого имеют доступ к закрытым или защищенным членам другого класса:

В этом примере есть еще кое-что новое: в начале программы находится пустое объявление класса Square. Это необходимо, так как класс Rectangle использует Square (как параметр метода convert), и Square использует Rectangle (объявляя его другом).

Дружественные отношения никогда не возникают без явного указания: В нашем примере класс Rectangle считается дружественным классу Square, но класс Square не считается дружественным классу Rectangle. Таким образом, методы Rectangle могут получить доступ к защищенным и закрытым членам Square, но не наоборот. Конечно, Square также может быть объявлен другом Rectangle, если необходимо предоставление такого доступа.

Другое свойство дружественный отношений это то, что они нетранзитивны: друг друга не считается другом без явного указания.

Наследование между классами

Классы в C++ могут быть расширены путем создания новых классов, сохраняющих характеристики базового класса. Этот процесс, известный как наследование, включает в себя базовый класс и производный класс. Производный класс наследует члены базового класса, в дополнение к которым он может добавить свои собственные члены.

Например, давайте представим ряд классов для описания двух типов многоугольников: прямоугольник и треугольник. Эти два многоугольника имеют некоторые общие свойства, такие как значения, необходимые для вычисления их площадей: в обоих случаях их можно описать как высота и ширина (или основание). Это может быть представлено в мире классов классом Polygon, из которого мы получим два других: Rectangle и Triangle:


Класс Polygon будет содержать члены, которые являются общими для обоих типов многоугольников. В нашем случае: ширина и высота. Rectangle и Triangle будут производными классами, которые определяют особенности, которые отличают один тип многоугольника от другого.

Классы, производные от других, наследуют все доступные члены базового класса. Это означает, что если базовый класс включает член A и мы наследуем класс от него с другим членом, называемым B, производный класс будет содержать как член A, так и член B.

Отношение наследования двух классов объявляется в производном классе. Определение производного класса использует следующий синтаксис:

class derived_class_name: public base_class_name
< /*. */ >;

Где derived_class_name это имя производного класса, а base_class_name это имя базового класса, на котором он основан. Спецификатор доступа public может быть заменен любым другим спецификатором доступа (protected или private). Этот спецификатор доступа ограничивает наибольший уровень доступа для членов, наследуемых от базового класса: Члены с большим уровнем доступа наследуются с этим уровнем, в то время как члены с равным или меньшим уровнем доступа сохраняют уровень ограничений в производном классе.

Объекты классов Rectangle и Triangle содержат члены, унаследованные от Polygon. Это: width, height и set_values.

Модификатор доступа protected используется в классе Polygon аналогично private. Единственное различие заключается в наследовании: когда класс наследуется от другого, члены производного класса имеют доступ к защищенным членам, наследуемым от базового класса, но не к закрытым.

Мы можем описать различные типы доступа согласно тому, как функции могут получить доступ к ним:

Где "не члены" представляет собой любой доступ извне класса, например, из main(), из другого класса или функции.

В примере выше члены, наследуемые Rectangle и Triangle, имеют такой же уровень доступа, как и в базовом классе Polygon:

Это так, потому что отношение наследования объявлено с использованием ключевого слова public для каждого из производных классов:

Это ключевое слово public после двоеточия (:) означает, что наивысший уровень доступа, который члены наследуют от класса, имя которого следует за ним (в данном случае Polygon), будет доступен в производном классе (в данном случае Rectangle). Так как public является наивысшим уровнем доступа, производный класс будет наследовать все члены с таким же уровнем доступа, который они имеют в базовом классе.

В случае protected, все открытые члены базового класса наследуются как защищенные в производном классе. Если задан уровень доступа private, все члены базового класса наследуются как private.

Например, если Daughter это класс, производный от Mother, который определен как:

тогда protected устанавливается как ограничитель уровня доступа для членов Daughter, которые наследуются от Mother. Все члены, которые имеют модификатор доступа public в классе Mother станут защищенными в Daughter. Конечно, это не запрещает объявлять собственные открытые члены в классе Daughter. Это ограничение уровня доступа устанавливается только для членов, наследуемых от Mother.

Если для наследования не указан уровень доступа, компилятор устанавливает private для классов, объявленных ключевым словом class, и public для тех, что объявлены как struct.

На самом деле, большинство случаев наследования в C++ использует наследование public. Если необходимы другие уровни доступа для базовых классов, они обычно могут быть представлены как поля класса.

Что наследуется от базового класса?

В принципе, производный класс без ограничения доступа (public) наследует доступ ко всем членам базового класса, за исключением:

  • конструкторов и деструктора;
  • оператора присваивания (operator=);
  • друзей;
  • закрытых членов;

Если не указано иное, конструкторы производного класса вызывают конструктор по умолчанию базового класса (т.е. конструктор без аргументов).

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

derived_constructor_name (parameters) : base_constructor_name (parameters)

Отметим различия между вызовами конструктора Mother при создании нового объекта Daughter и объекта Son. Это различие существует по причине различных объявлений конструкторов классов Daughter и Son.

Множественное наследование

Класс может наследовать более одного класса простым перечислением нескольких базовых классов, разделенных запятыми, в списке базовых классов (т.е. после двоеточия). Например, если программа имеет определенный класс Output для вывода на экран, и мы хоте ли бы, чтобы наши классы Rectangle и Triangle также наследовали и его члены в дополнение к членам Polygon, мы можем написать:

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

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

Идея наследования реализует это отношения. Например, млекопитающее животное IS-A, млекопитающее собаки IS-A, следовательно, животное IS-A собаки и так далее.

Базовые и производные классы

Класс может быть получен из более чем одного класса, что означает, что он может наследовать данные и функции из нескольких базовых классов. Чтобы определить производный класс, мы используем список производных классов, чтобы указать базовый класс (es). Список дериваций классов называет один или несколько базовых классов и имеет форму -

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

Рассмотрим базовый класс Shape и его производный класс Rectangle следующим образом:

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Контроль доступа и наследование

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

Мы можем суммировать различные типы доступа в зависимости от того, кто может получить к ним доступ следующим образом:

§ 28. Агрегация и наследование. Рассмотрим пример. Положим, мы создаем программу – редактор векторной графики. Для представления геометрических фигур используются следующие классы: многоугольник Polygon , треугольник Triangle , круг Circle . Вершины многоугольников и центр круга представляются классом точки Point . Положим также, мы реализуем для всех фигур метод смещения void Move (float dx, float dy) , который должен перемещать все точки фигуры на указанные величины по X и Y. Например, этот метод будет использоваться при перемещении фигуры курсором. Проанализируем следующий код.

Сравните методы Move в классах Circle , Polygon и Triangle . Хотя их код отличается, эти методы логически выполняют одно и то же: перебирают все поля-точки класса соответствующей фигуры и вызывают для каждой из этих точек метод Move . Изменив способ хранения данных в классах Triangle и Circle на массив точек, мы сделаем код методов Move одинаковым во всех трех классах:

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

Таким способом мы частично решили поставленную задачу. Почему частично? Во-первых, метод Move все равно повторяется в каждом классе, хотя и свелся к одной строке вызова метода Move объекта shape . То есть мы минимизировали, но не исключили дублирование кода. Во-вторых, синтаксически ничто не мешает нам назвать эти методы по-разному в разных классах, то есть семантическая связь между классами фигур не гарантируется синтаксически. Конечно, мы можем полностью исключить повторяющиеся методы Move , объявив поле Shape как открытое, и удалив метод Move из всех классов, кроме Shape :

Такое решение синтаксически гарантирует, что метод Move и другие общие для всех фигур методы из класса Shape будут иметь одно имя при обращении из рассматриваемых классов. Однако само поле Shape мы вполне можем назвать по-разному в разных классах.

На следующем рисунке показана диаграмма объектов и соответствующее распределение памяти для экземпляра класса Circle (прямоугольники с подчеркнутым названием в нотации UML обозначают объекты, при этом после двоеточия указывается их тип – класс, а перед двоеточием необязательно может быть указано имя объекта):

Разберем синтаксис, используемый в классе Circle5 .

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

Открытые поля и методы объекта типа Shape доступны в методах класса Circle5 как если бы они были объявлены в нем же. При этом они вызываются через неявное обращение к переменной base , обозначающей экземпляр базового класса, по аналогии с тем, как мы обращаемся к полям класса из его методов через неявное обращение к переменной this , обозначающей экземпляр того же класса. Сравните это с вариантом реализации без наследования, где мы объявляем и инициализируем поле Shape base явно – поведение программы в обоих случаях будет идентично. Приведем еще пример:

Таким образом, наш класс Circle5 не просто неявно включает в себя экземпляр Shape base , но наследует его поведение (методы) и состояние (поля). Закрытые методы и поля базового класса ( vertices ) не видны, как и в случае агрегации, однако, как мы покажем в следующем параграфе, возможно отметить поля базового класса, чтобы они были видны в производном, но не видны пользователям этого производного класса.

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

Сформулируем следующее определение.

Отношение наследования изображается в нотации UML не закрашенной треугольной стрелкой в направлении от производного класса к базовому.

На следующем рисунке показана диаграмма объектов и соответствующее распределение памяти для экземпляра класса Circle :

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

§ 29. Защищенные поля и методы. В рассмотренных в предыдущем параграфе примерах массив точек Point[] vertices в классе Shape не виден производным классам, так как он объявлен закрытым private . Однако, для многих задач нам хотелось бы иметь доступ к состоянию и поведению (или к части состояния и поведения – к некоторым полям и методам) базового класса из производного, но не раскрывать это состояние и поведение для пользователей производного. То есть объявить поле или метод, которое было бы открытым ( public ) для методов производного класса, но закрытым ( private ) для других классов.

Например, для класса Circle5 мы бы хотели объявить метод public Point GetCenter() , возвращающий точку-центр круга. Но делать массив Shape.vertices открытым или создавать открытый метод получения точек public Point Shape.GetVertex(int) мы бы не хотели, так как тогда этот массив или соответствующий метод оказались бы видны пользователям класса. Чего, в свою очередь, мы бы хотели избежать, так как наличие у класса круга открытого поля-массива вершин или метода, возвращающего вершину по индексу, было бы нелогично:

Эта задача решается механизмом защищенных ( protected ) полей и методов.

Защищенные (protected) поля и методы – поля и методы, которые видны в методах класса, в котором они объявлены и в методах всех производных классов, но не видны извне этих классов.

Защищенные поля и методы помечаются модификатором видимости protected . В следующей таблицы приведена сводная информация по модификаторам видимости полей и методов:

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

§ 30. Многоуровневое наследование. В объектно-ориентированном программировании допускается многоуровневое наследование (multilevel): базовые классы могут быть производными по отношению к другим классам.

При многоуровневом наследовании открытые и защищенные поля и методы видны всем производным классам по иерархии наследования. Рассмотрим следующий пример с четырехуровневым наследованием:

Соответствующая диаграмма классов:

Поле fieldB класса B не видно в классе A , так как это базовый, а не производный класс, по отношению к B , но оно видно в классах C , D1 и D2 , так как все они производные по отношению к классу B . Аналогично наследуется поле fieldC .

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

§ 32. Механизм создания экземпляра производного класса. В примере с геометрическими фигурами в § 28 мы уже указали, что при создании объекта производного класса сначала вызывается конструктор базового класса, а потом – конструктор производного. Приведем рассмотренный фрагмент кода повторно:

Оператор new сначала определяет, какой конструктор базового класса Shape соответствует вызванному конструктору производного класса Circle5 . Здесь мы вызвали конструктор по умолчанию класса Circle5 , и в определении этого конструктора указано, что ему соответствует конструктор Shape(int) базового класса. Поэтому сначала вызывается конструктор Shape(int) , а затем выполняется тело конструктора Circle5() . Однако такое описание не отвечает на следующие вопросы: 1) какова последовательность выполнения конструкторов при многоуровневом наследовании и 2) когда и в какой последовательности выполняется выделение памяти и инициализация полей классов?

При создании экземпляра класса C получим следующий вывод в консоль:

Обратим внимание, что в классе A объявлено два конструктора. При конструировании объекта класса C используется конструктор A(int) , так как именно он указан как соответствующий конструктор базового класса для конструктора B() , который, в свою очередь, указан как соответствующий конструктор для конструктора C() , который и вызван оператором new . Проиллюстрируем этот механизм следующим рисунком:

Если указанный конструктор базового класса не существует, то это приведет к ошибке компиляции:

Также отметим, что конструкторы, как поля и методы, могут быть объявлены открытыми ( public ), закрытыми ( private ) или защищенными ( protected ). Пример использования закрытых конструкторов мы рассматривали в главе 2.6. Здесь же обратим внимание, что производный класс не может обратиться к закрытому конструктору базового класса, так же как он не может обращаться к закрытым полям и методам базового класса.

В результате получим следующее распределение памяти:

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

Обратим внимание, что так как инициализация полей выполняется в порядке от производных классов к базовым, то в инициализаторе поля мы не можем обращаться к открытым и защищенным полям базовых классов, так как они в момент вызова инициализатора еще не существуют:

В заключение сведем все вместе и сформулируем алгоритм конструирования объекта производного класса, уточняющий алгоритм, рассмотренный нами ранее в главе 2.2:

Последний выполнившийся конструктор возвращает адрес созданного объекта.

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

Вопросы и задания

Дайте определения следующим терминам, а также сопоставьте русские и английские термины: агрегация, наследование, базовый класс, производный класс, суперкласс, подкласс, защищенные поля, методы и конструкторы, многоуровневое наследование, множественное наследование; aggregation, inheritance, base class, derived class, superclass, subclass, protected field, methods and constructors, multilevel inheritance, multiple inheritance.

Наследуются ли конструкторы классов?

Какой модификатор видимости имеет ключевое слово base ? Допустимо ли обращение к базовому классу базового класса через выражение base.base ?

Что будет выведено в консоль после выполнения следующего кода:

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

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

БлогNot. Лекции по C/C++: Классы, часть 2 (наследование)

Лекции по C/C++: Классы, часть 2 (наследование)

Производный (derived) класс — это расширение существующего базового класса или класс-потомок. Он может модифицировать права доступа к данным родительского класса, добавить новые свойства или переопределить (перегрузить) существующие у родителя методы. Описание потомка выглядит следующим образом:

Здесь БазовыйСписок содержит перечень разделенных запятой спецификаторов атрибутов доступа ( public , protected или private ) и имен базовых классов:

Производный класс наследует все члены перечисленных базовых классов, но может использовать только члены с атрибутом public или protected .

Класс обычно наследуется как public или как private . При этом модификатор private трансформирует компоненты базового класса с атрибутами доступа public и protected в компоненты private производного класса, в то время как private -компоненты становятся недоступны в производном классе.

Модификатор наследования public не изменяет уровня доступа. Производный класс наследует все компоненты своего базового класса, но может непосредственно использовать только те из них, которые определены с атрибутами public и protected . Подробнее особенности наследования рассмотрены ниже в примере 6.

Пример 1. Для дальнейшей работы используем класс Student из предыдущей лекции и вынесем его описание в заголовочный файл student.h

В реальных проектах так делают всегда и всегда интерфейс класса (свойства и прототипы методов) описан в файле имякласса.h , а реализация (тела функций-методов) - в файле имякласса.cpp

Теперь представим, что у класса "Студент" есть класс-потомок "Студент, имеющий хобби", а само дополнительное свойство "хобби" представляет собой односимвольный идентификатор типа char . Назовём класс-потомок Hobbit и включим его описание в файл student.h , который примет следующий вид:

Итак, в student.h здесь добавлен прототип класса-потомка Hobbit для класса-родителя Student .

Обратите внимание, что конструкторы класса-потомка используют соответствующие конструкторы родителя, вызывая их через список инициализации:

Дополнительные методы setHobby , getHobby будут работать с добавленным свойством Hobby . Недостающий перегруженный метод showStudent будет написан чуть позже.

Добавим в проект файл исходного кода student.cpp и внесём туда код, написанный для базового класса:

Комментарии к коду:

1. Конструктор по умолчанию инициализирует свойства базового класса пустыми значениями.

2. Конструктор с параметрами выделяет память для свойства Name и копирует туда строку, переданную в конструктор параметром newName .

3. Конструктор копирования проверяет, не пусто ли свойство Name у объекта справа от знака " = ", если это так, свойство Name текущего объекта ставится в NULL . Это должно обеспечить корректное копирование и "пустых" объектов, например

Кроме того, конструктор копирования не должен просто вызывать setName для установки свойства Name – ведь у объекта слева от знака " = " свойство Name может быть ещё не определено и содержать "мусор", не равный, в том числе, и значению NULL .

4. Метод setName всегда вызывается для уже "сконструированного" объекта, поэтому он может освободить память, занятую текущим именем Name .

5. Метод вывода информации учитывает, что может потребоваться вывод объекта, у которого не установлено свойство Name .

Замечание: как правило, на практике в проект добавляется объект "Класс C++", сразу же содержащий файлы .cpp и .h и эта пара файлов содержит описание одного класса.

Непосредственно после этого кода в файле student.cpp напишем реализацию недостающего метода класса-потомка, а также продемонстрируем функцию, не являющуюся членом класса, но создающую его объект:

Код главного файла проекта, например, с именем main.cpp :

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

Пример 2. Функция-друг класса.

В нашем проекте для объявления другом класса Hobbit функции Construct также было бы достаточно добавить её прототип с ключевым словом friend в описание класса Hobbit :

Чрезмерное использование "друзей" не рекомендуется, так как не соответствует требованиям к безопасности кода.

Виртуальные функции. Для написания иерархии классов часто применяются виртуальные методы. Такие функции базового класса, объявленные с ключевым словом virtual , специально предназначены для реализации в классах-потомках. Например, метод draw ("нарисовать") у базового класса "геометрическая фигура" разумно сделать виртуальным, чтобы каждый из классов-наследников "прямоугольник" и "окружность" мог реализовать свой собственный метод отрисовки. Но все методы будут вызываться одним ключевым словом, что удобно. Кроме того, в ряде случаев следует объявлять виртуальными явные деструкторы (см. ниже).

Если функцию объявить виртуальной и перегрузить её в классе-потомке, то при её вызове через указатель на базовый класс будет вызываться именно та версия функции, которая определена в классе-потомке. Иначе была бы вызвана та версия, которая определена в базовом классе.

Встраиваемые функции. Объявляются в описании класса с ключевым словом inline . Встретив такое описание, компилятор может встроить тело функции непосредственно в точку вызова. Таким образом, объём кода увеличивается, но за счёт отказа от вызова функций и передачи параметров через стек, код может работать быстрее. Чаще всего встраивание применяется для небольших по размеру функций. Cовременные компиляторы могут игнорировать указание директивы inline в случаях, когда встраивание противоречит текущим правилам оптимизации кода.

Кроме того, функцию с модификатором inline можно помещать в файл *.h . Даже если функция не будет встроена, её множественное определение в разных модулях компиляции не должно вызывать проблем при сборке приложения.

Пример 3. Встраиваемые и виртуальные методы. Пример также показывают "упрощённую" работу со строковыми полями – память под свойство name всегда выделяется фиксированного размера MAXSIZE .

Если из класса Sinok исключить метод show , при вызове s->show(); будет вызываться метод родителя, печатающий только имя.

При создании иерархии классов деструктор базового класса должен быть всегда виртуальным.

Пример 4. Виртуальный деструктор базового класса

Эта программа напечатает: A()B()~A()

То есть, деструктор класса B не вызывался, соответственно, он мог не освободить память, если таковая выделялась конструктором!

Изменив деструктор класса A на

получим вывод A()B()~B()~A()

  • При наличии хотя бы одного виртуального метода объявлять виртуальным и деструктор;
  • Также его надо явно объявлять виртуальным, если класс предполагается в будущем сделать базовым.

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

Пример 5. Класс-окружность и его наследник - цилиндр.

Пример 6. Иерархия классов и наследование с разными атрибутами доступа.

Читайте также: