Порядок вызова конструкторов и деструкторов при наследовании

Обновлено: 02.05.2024

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Наследование - механизм передачи свойств одних классов другим классам в процессе проектирования иерархии классов. Исходные классы называют базовыми(БК) (родителями), вторые - производными (ПК) (потомками).

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

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

Для удобного изображения отношений классов при наследовании строят специальный граф: дерево наследования:


Рисунок 1. Дерево наследования

Базовые классы изображают над производными.

Пример с наследованием

При описании производного класса в его заголовке перечисляются все базовые для него классы. Доступ к элементам этих классов регулируется с помощью ключей доступа: private , protected и public . Если базовых классов несколько, они перечисляются через запятую.

Использование ключей доступа вместе с разделами внутри БК позволяет сформировать правило, по которому будет осуществляться доступ к членам ПК.

Ключ доступа Раздел БК Раздел ПК
private private нет
private protected private
private public private
protected private нет
protected protected protected
protected public protected
public private нет
public protected protected
public public public

Из таблицы следует, что если, например, в БК некоторая переменная располагалась в разделе public, а ПК был объявлен с ключом private, то в ПК к данной переменной можно будет обращаться только членам ПК или его друзьям (эта переменная перейдет в раздел private ПК). Когда происходит наследование без явного указания спецификатора, все имена базового класса в производном классе автоматически становятся приватными (или можно указать private).

Если наследовать с ключевым словом public - все общедоступные имена базового класса будут общедоступными в производном классе и все защищенные имена будут защищенными в производном классе.

  • конструкторы
  • деструкторы
  • перегруженные new
  • перегруженные =
  • отношения дружественности

Правила для специальных методов

  • Поскольку конструкторы не наследуются, ПК должен иметь собственные конструкторы.
  • Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор БК.
  • Для иерархии, состоящей из нескольких классов, конструкторы БК вызываются начиная с самого верхнего уровня. После этого выполняются конструкторы элементов класса, являющихся объектами, а затем - конструктор класса.
  • В случае нескольких БК их конструкторы вызываются в порядке объявления.
  • Для деструкторов эти правила справедливы, но порядок вызова обратный - сначала ПК, затем БК. Не требуется явно вызывать деструкторы, поскольку они будут вызваны автоматически.

Передача параметра в конструктор БК

Данная ситуация возникает в том случае, когда конструктор БК должен быть вызван с параметром (параметрами). При записи конструктора ПК необходимо указать через двоеточие имя конструктора БК со списком фактических параметров.

Работа с производными классами

Рассмотрим класс Работник , который содержит персональные данные, а также дату принятия на работу и увольнения

На основе этого класса создадим Программиста

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

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

Производные классы и указатели

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

Копируется только Employee-часть Programmer – срезка.

Рассмотрим небольшую иерархию должностей в софтверной компании:


Рисунок 2. Иерархия должностей


Рисунок 3. таблица виртуальных функций

Любой класс, который использует виртуальные функции (или дочерний класс, родительский класс которого использует виртуальные функции), имеет свою собственную виртуальную таблицу. Это обычный статический массив, который создается компилятором во время компиляции. Виртуальная таблица содержит по одной записи на каждую виртуальную функцию, которая может быть вызвана объектами класса. Каждая запись в этой таблице — это указатель на функцию, указывающий на метод, доступный объекту этого класса.

Обычно компилятор создает отдельную vtable для каждого класса. После создания объекта указатель на эту vtable, называемый виртуальный табличный указатель, добавляется как скрытый член данного объекта (а зачастую как первый член). Компилятор также генерирует «скрытый» код в конструкторе каждого класса для инициализации указателей его объектов адресами соответствующей vtable.

Если виртуальная функция не переопределена в производном классе, vtable производного класса хранит адрес функции в родительском классе. Таблица vtable используется для получения доступа к адресу при вызове виртуальной функции. Механизм vtable позволяет реализовать динамическое связывание в C++.

Когда мы связываем объект производного класса с указателем базового класса, переменная vtbl указывает на vtable производного класса. Это присвоение гарантирует, что будет вызвана нужная виртуальная функция.

Как использовать виртуальные функции?

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

Рассмотрим другой пример:

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

Ключевые слова final и override

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

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

Нельзя создать экземпляр абстрактного класса.

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

Под интерфейсом, в узком смысле, понимается абстрактый класс, в котором:

  • все методы чисто виртуальные
  • нет полей с данными
  • все методы открытые (public)

В случае наследования от интерфейса, класс обязан реализовать его методы, иначе он остается абстрактным.

Виртуальные конструкторы и деструкторы

Виртуальные деструкторы обеспечивают корректное освобождение ресурсов при применении delete к указателю на базовый класс

На самом деле, таких не существует!

Производный класс имеет доступ к защищенным членам базового (но только для объектов собственного типа)

Защищенные данные приводят к проблемам сопровождения

Защищенные функции - хороший способ задания операций для использования в производных классах

Открытое наследование делает производный класс подтипом базового

Защищенное и закрытое наследование используются для выражения деталей реализации

Если порожденный класс наследует элементы одного базового класса, то такое наследование называется одиночным . Однако, возможно и множественное наследование. Множественное наследование позволяет порожденному классу наследовать элементы более, чем от одного базового класса. Синтаксис заголовков классов расширяется так, чтобы разрешить создание списка базовых классов и обозначения их уровня доступа:

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

Класс А обобщенно наследует элементы всех трех основных классов.

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

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


В этих случаях необходимо использовать оператор разрешения контекста для уточнения элемента, к которому осуществляется доступ, именем базового класса.

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

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

Деструкторы вызываются в порядке обратном вызову конструкторов.

Виртуальные базовые классы

Иерархия наследования

Базовый класс может быть задан только один раз в списке порождения нового класса. Однако базовый класс может встретиться несколько раз в иерархии порождения.

Такая иерархия порождения несет двусмысленность при доступе к наследуемым членам класса X и может привести к ошибкам. В этом случае класс X будет дважды присутствовать в A . Хорошо это или плохо - зависит от решаемой задачи.

Если двойное вхождение объектов класса X в объект класса А не является допустимым, существует два выхода для разрешения такой ситуации:

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

Базовый класс определяется как виртуальный заданием ключевого слова virtual в списке порождения перед именем базового класса или указанием типа наследования


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

class A : public Y, public Z int key;
public :
A( int i = 0) : Y(i + 2), Z(i + 3)
key = Y::key + Z::key;
>
int getkey( void ) < return (key); >
>;
int main() A object(4);
cout cin.get();
return 0;
>

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

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

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

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

Наследование

Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (иногда порождающими), а новые классы, формируемые на основе базовых, – производными (порожденными, классами-потомками или наследниками).


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

Для порождения нового класса на основе существующего используется следующая общая форма

При объявлении порождаемого класса МодификаторДоступа может принимать значения public , private , protected либо отсутствовать, по умолчанию используется значение private . В любом случае порожденный класс наследует все члены базового класса, но доступ имеет не ко всем. Ему доступны общие ( public ) члены базового класса и недоступны частные ( private ).

Для того, чтобы порожденный класс имел доступ к некоторым скрытым членам базового класса, в базовом классе их необходимо объявить со спецификацией доступа защищенные ( protected ).

Члены класса с доступом protected видимы в пределах класса и в любом классе, порожденном из этого класса.

Общее наследование

При общем наследовании порожденный класс имеет доступ к наследуемым членам базового класса с видимостью public и protected . Члены базового класса с видимостью private – недоступны.

Спецификация доступа внутри класса в порожденном классе вне класса
private +
protected + +
public + + +

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

Порожденный класс наследует все данные класса student (строка 13), имеет доступ к protected и public — членам базового класса. В новом классе добавлено два поля данных (строки 16, 17), и порожденный класс переопределяет функцию print() (строки 20, 39-43).

Конструктор для базового класса вызывается в списке инициализации (строка 29).

Но что происходит, когда мы присваиваем указателю класса student ссылку на объект класса grad_student (строка 55)? В этом случае происходит преобразование указателей, и в строке 56 вызывается уже функция print() класса student .


Указатель на порожденный класс может быть неявно передан в указатель на базовый класс. А указатель на порожденный класс может указывать только на объекты порожденного класса. То есть обратное преобразование недопустимо

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

  • объект порожденного класса неявно преобразуется к объекту базового класса.
  • ссылка на порожденный класс неявно преобразуется к ссылке на базовый класс.
  • указатель на порожденный класс неявно преобразуется к указателю на базовый класс.

Частное наследование

Порожденный класс может быть базовым для следующего порождения. При порождении private наследуемые члены базового класса, объявленные как protected и public , становятся членами порожденного класса с видимостью private . При этом члены базового класса с видимостью public и protected становятся недоступными для дальнейших порождений. Цель такого порождения — скрыть классы или элементы классов от использования их в дальнейших порождениях. При порождении private не выполняются предопределенные стандартные преобразования:

Однако порождение private позволяет отдельным элементам базового класса с видимостью public и protected сохранить свою видимость в порожденном классе. Для этого необходимо

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

class X
private :
int n;
protected :
int m;
char s;
public :
void func( int );
>;
class Y : private X
private :
.
protected :
.
X::s;
public :
.
X::func();
>;

Возможен и третий вариант наследования – с использованием модификатора доступа protected .

Доступ к элементам базового класса из производного класса, в зависимости от модификатора наследования:

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

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

Если и у базового и у производного классов есть конструкторы и деструкторы, то конструкторы выполняются в порядке наследования, а деструкторы – в обратном порядке. То есть если А – базовый класс, В – производный из А , а С – производный из В ( А-В-С ), то при создании объекта класса С вызов конструкторов будет иметь следующий порядок:

  • конструктор класса А
  • конструктор класса В
  • конструктор класса С .

Вызов деструкторов при удалении этого объекта произойдет в обратном порядке:

  • деструктор класса С
  • деструктор класса В
  • деструктор класса А .

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

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

КонструкторПроизводногоКласса (СписокФормальныхАргументов)
: КонструкторБазовогоКласса (СписокФактическихАргументов)
< // тело конструктора производного класса >

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

Конструктор производного класса не должен использовать все аргументы, часть предназначены для передачи в базовый класс (строка 29, см. код выше). В расширенной форме объявления конструктора производного класса описывается вызов конструктора базового класса.

Здравствуйте! а подскажите, пожалуйста, как я могу объявить неполный наследуемый класс? Например, у меня есть class a b class b, наследуемый откласса а. я хочу неполно объявить класс b перед классом а. как это синтаксически сделать? нигде не могу найти инфу об этом.

В этой статье наследование описано на трех уровнях: beginner, intermediate и advanced. Expert нет. И ни слова про SOLID. Честно.

Что такое наследование?

Наследование является одним из основополагающих принципов ООП. В соответствии с ним, класс может использовать переменные и методы другого класса как свои собственные.

Класс, который наследует данные, называется подклассом (subclass), производным классом (derived class) или дочерним классом (child). Класс, от которого наследуются данные или методы, называется суперклассом (super class), базовым классом (base class) или родительским классом (parent). Термины “родительский” и “дочерний” чрезвычайно полезны для понимания наследования. Как ребенок получает характеристики своих родителей, производный класс получает методы и переменные базового класса.

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

В этом примере, метод turn_on() и переменная serial_number не были объявлены или определены в подклассе Computer . Однако их можно использовать, поскольку они унаследованы от базового класса.

Важное примечание: приватные переменные и методы не могут быть унаследованы.

Типы наследования

В C ++ есть несколько типов наследования:

  • публичный ( public )- публичные ( public ) и защищенные ( protected ) данные наследуются без изменения уровня доступа к ним;
  • защищенный ( protected ) — все унаследованные данные становятся защищенными;
  • приватный ( private ) — все унаследованные данные становятся приватными.

Для базового класса Device , уровень доступа к данным не изменяется, но поскольку производный класс Computer наследует данные как приватные, данные становятся приватными для класса Computer .

Класс Computer теперь использует метод turn_on() как и любой приватный метод: turn_on() может быть вызван изнутри класса, но попытка вызвать его напрямую из main приведет к ошибке во время компиляции. Для базового класса Device , метод turn_on() остался публичным, и может быть вызван из main .

Конструкторы и деструкторы

В C ++ конструкторы и деструкторы не наследуются. Однако они вызываются, когда дочерний класс инициализирует свой объект. Конструкторы вызываются один за другим иерархически, начиная с базового класса и заканчивая последним производным классом. Деструкторы вызываются в обратном порядке.

Важное примечание: в этой статье не освещены виртуальные десктрукторы. Дополнительный материал на эту тему можно найти к примеру в этой статье на хабре.

Конструкторы: Device -> Computer -> Laptop .
Деструкторы: Laptop -> Computer -> Device .

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

Множественное наследование происходит, когда подкласс имеет два или более суперкласса. В этом примере, класс Laptop наследует и Monitor и Computer одновременно.

Проблематика множественного наследования

Множественное наследование требует тщательного проектирования, так как может привести к непредвиденным последствиям. Большинство таких последствий вызваны неоднозначностью в наследовании. В данном примере Laptop наследует метод turn_on() от обоих родителей и неясно какой метод должен быть вызван.

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

Проблема ромба

Проблема ромба (Diamond problem)- классическая проблема в языках, которые поддерживают возможность множественного наследования. Эта проблема возникает когда классы B и C наследуют A , а класс D наследует B и C .

К примеру, классы A , B и C определяют метод print_letter() . Если print_letter() будет вызываться классом D , неясно какой метод должен быть вызван — метод класса A , B или C . Разные языки по-разному подходят к решению ромбовидной проблем. В C ++ решение проблемы оставлено на усмотрение программиста.

Ромбовидная проблема — прежде всего проблема дизайна, и она должна быть предусмотрена на этапе проектирования. На этапе разработки ее можно разрешить следующим образом:

  • вызвать метод конкретного суперкласса;
  • обратиться к объекту подкласса как к объекту определенного суперкласса;
  • переопределить проблематичный метод в последнем дочернем классе (в коде — turn_on() в подклассе Laptop ).

Если метод turn_on() не был переопределен в Laptop, вызов Laptop_instance.turn_on() , приведет к ошибке при компиляции. Объект Laptop может получить доступ к двум определениям метода turn_on() одновременно: Device:Computer:Laptop.turn_on() и Device:Monitor:Laptop.turn_on() .

Проблема ромба: Конструкторы и деструкторы

Поскольку в С++ при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов, возникает и другая проблема: конструктор базового класса Device будет вызван дважды.

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

Виртуальное наследование (virtual inheritance) предотвращает появление множественных объектов базового класса в иерархии наследования. Таким образом, конструктор базового класса Device будет вызван только единожды, а обращение к методу turn_on() без его переопределения в дочернем классе не будет вызывать ошибку при компиляции.

Примечание: виртуальное наследование в классах Computer и Monitor не разрешит ромбовидное наследование если дочерний класс Laptop будет наследовать класс Device не виртуально ( class Laptop: public Computer, public Monitor, public Device <>; ).

Абстрактный класс

В С++, класс в котором существует хотя бы один чистый виртуальный метод (pure virtual) принято считать абстрактным. Если виртуальный метод не переопределен в дочернем классе, код не скомпилируется. Также, в С++ создать объект абстрактного класса невозможно — попытка тоже вызовет ошибку при компиляции.

Интерфейс

С++, в отличии от некоторых ООП языков, не предоставляет отдельного ключевого слова для обозначения интерфейса (interface). Тем не менее, реализация интерфейса возможна путем создания чистого абстрактного класса (pure abstract class) — класса в котором присутствуют только декларации методов. Такие классы также часто называют абстрактными базовыми классами (Abstract Base Class — ABC).

Несмотря на то, что наследование — фундаментальный принцип ООП, его стоит использовать с осторожностью. Важно думать о том, что любой код который будет использоваться скорее всего будет изменен и может быть использован неочевидным для разработчика путем.

Наследование от реализованного или частично реализованного класса

Если наследование происходит не от интерфейса (чистого абстрактного класса в контексте С++), а от класса в котором присутствуют какие-либо реализации, стоит учитывать то, что класс наследник связан с родительским классом наиболее тесной из возможных связью. Большинство изменений в классе родителя могут затронуть наследника что может привести к непредвиденному поведению. Такие изменения в поведении наследника не всегда очевидны — ошибка может возникнуть в уже оттестированом и рабочем коде. Данная ситуация усугубляется наличием сложной иерархии классов. Всегда стоит помнить о том, что код может изменяться не только человеком который его написал, и пути наследования очевидные для автора могут быть не учтены его коллегами.

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

Интерфейс

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

Интерфейс: Пример использования

Прежде всего стоит заметить, что пример тесно связан с понятием полиморфизма, но будет рассмотрен в контексте наследования от чистого абстрактного класса.

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

Отсутствие однозначности касательно форматирования конфигурационного файла не тормозит процесс разработки основной программы. Два разработчика могут работать параллельно — один над бизнес логикой, а другой над парсером. Поскольку они взаимодействуют через этот интерфейс, каждый из них может работать независимо. Данный подход облегчает покрытие кода юнит тестами, так как необходимые тесты могут быть написаны с использованием мока (mock) для этого интерфейса.

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

Наследование предоставляет множество преимуществ, но должно быть тщательно спроектировано во избежание проблем, возможность для которых оно открывает. В контексте наследования, С++ предоставляет широкий спектр инструментов который открывает массу возможностей для программиста.

Как вы уже знаете, дочерний класс наследует все данные (поля) и весь набор методов базового класса, включая операторы. Иными словами, объект дочернего класса является объектом базового класса с дополнительной информацией и/или дополнительным набором методов.

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

Рассмотрим как себя ведут конструкторы базового и дочернего класса.

У базового класса Base есть конструктор с параметрами:

При создании дочернего класса, для него требуется описать собственный конструктор:

В примере, для дочернего класса определены два конструктора:
— конструктор по умолчанию
— конструктор с параметрами
Как Вы могли заметить, в списке инициализации конструктора дочернего класса явно вызывает конструктор базового класса. Это связано с тем, что при создании экземпляра дочернего класса, в первую очередь, будет создан объект базового класса, то есть будет вызван конструктор базового класса и только затем вызван конструктор дочернего класса. Так как в базовом классе есть только конструктор с параметрами, то его нужно явно вызывать для инициализации экземпляра.
Понимайте это так: экземпляр дочернего класса — это обертка над объектом базового класса, которая добавляет новые поля и методы к экземпляру базового класса. Обертка не может быть создана сама по себе, сначала вызывается конструктор базового класса, чтобы создать объект базового класса, и только после этого вызывается конструктор дочернего класса, чтобы обернуть объект базового класса дополнительными полями.

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

Запомните: при создании экземпляра дочернего класса конструктор базового класса вызывается первым!

Рассмотрим как себя ведут деструкторы базового и дочернего класса.

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

При удалении экземпляра дочернего класса будет неявно вызываться деструктор базового класса.
Явного вызова деструктора дочернего класса не требуется!
Порядок вызова деструкторов: Первым вызывается деструктор дочернего класса и только затем деструктор базового класса. Получается, что порядок вызова деструкторов обратен порядку вызова конструкторов!

Поведение операторов копирования базового и дочернего класса.

Если Вы определяет оператор копирования у базового класса, то и для дочернего класса требуется определить собственный оператор копирования.
В операторе копирования дочернего класса стоит явно вызвать оператор копирования базового класса, как это показано в примере (можно этого не делать, но тогда придется в операторе дочернего класса описать копирование полей базового класса, также как это сделано в операторе самого базового класса).

Если классы содержат только скалярный поля (нет массивов), то оператор копирования можно и не описывать, компилятор сам встроит правильную реализацию этого оператора.

Аналогично оператору копированию обстоят дела с оператором перемещения (С++11).

в примерах была опущена проверка вида:

Которая должная присутствовать как в операторе копирования, так и в операторе перемещения.
Данная проверка предотвращает ряд проблем в случаях:
a = a;
a = std::move(a);

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