
Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdf580 Часть Hi« Гфограмттровантв с агрегированием и наследованием
Данное решение представлено в листинге 13.5. Производный класс содержит две функции deposit О с двумя разными сигнатурами. Так как обе они принадле жат одному классу, действуют правила перегрузки имен. И новый, и существую щий программный код будет вызывать функции-члены
Итоговые балансы |
|
производного класса, применяя разные сигнатуры. Функ |
|
объект расчетного счета |
1294.6 |
ция-член с одним аргументом должна просто вызывать |
|
|
|
функцию-член базового класса с тем же именем (переда |
|
Рис. 13.12. Результат |
программы |
вая |
работу серверу). Результат программы показан на |
из лист,ипга |
18.15 |
рис. |
13.12. |
Листинг 13.15. Пример иерархии наследования классов Account
#include <iostream> using namespace std;
class Account { |
|
|
// базовый класс |
||
protected: |
|
|
|
|
|
double balance; |
|
|
|
||
public: |
|
|
|
|
|
Account(double initBalance = 0) |
|
|
|
||
{ balance = initBalance; } |
|
// наследуется без изменений |
|||
double getBalO |
|
||||
{ return balance; } |
|
// переопределяется в производном классе |
|||
void withdraw(double amount) |
|
||||
{ if (balance > amount) |
|
|
|
||
balance -=amount; } |
|
// наследуется без изменений |
|||
void deposit(double amount) |
|
||||
{ balance +=amount; } |
|
|
|
||
class CheckingAccount : public Account { |
// производный |
класс |
|||
double fee; |
|
|
|
|
|
public: |
|
|
|
|
|
CheckingAccount(double initBalance) |
|
|
|||
{ balance = initBalance; fee = 0.2; } |
|
|
|||
void withdraw(double amount) |
|
|
|
||
{ if (balance > amount) |
fee; } |
|
|
||
balance = balance - amount |
|
|
|||
void deposit(double amount) |
|
// скрывает метод базового класса |
|||
{ Account::deposit(amount); } |
|
// вызов базовой функции |
|||
void deposit(double amount, double fee) |
// скрывает метод базового класса |
||||
{ balance = balance + amount - fee - CheckingAccount::fee; } |
|||||
} |
|
|
|
|
|
int mainO |
|
|
|
|
|
CheckingAccount a1(1000); |
|
// скрывает базовый метод |
|||
a1.withdraw(100); |
|
// метод производного класса |
|||
a1.deposit(200); |
|
// существующий |
клиентский код |
||
a1.deposit(200,5); |
|
|
|
||
cout « |
" Итоговые балансы\п"; |
|
a1.getBal() « |
endl; |
|
cout « |
" |
объект CheckingAccount: « |
return 0;
}
Глава 13 • Подобные классы и их интерпретация |
581 |
Вас не должно пугать использование в этом примере операций области действия. Функцию depositO с одним параметром в классе CheckingAccount можно было бы записать следуюш.им образом:
void CheckingAccount::deposit(double |
amount) |
/ / скрывает базовый метод |
{ deposit(amount); } |
/ / |
бесконечный рекурсивный вызов |
Когда компилятор обрабатывает тело данной функции, в поисках соответствия он сравнивает имя depositO и локальное для функции имя. Среди локальных имен такого нет, поэтому компилятор иидет совпадение среди компонентов класса. Он находит имя CheckingAccount: :depositO и генерирует вызов этой функции.
Врезультате он интерпретируется как бесконечный рекурсивный вызов. Операция области действия в листинге 13.15 вынуждает компилятор генери
ровать вызов функции Account: :depositO базового класса, чтобы избежать ре курсии. Обратите внимание, что обязанности по организации иерархии классов и включению функции в тот или иной класс переносятся на серверные классы, а не на клиента, как в первом варианте.
Функцию depositO в классе CheckingAccount с двумя параметрами можно записать так:
void CheckingAccount::deposit(double |
amount, double fee) |
{ balance = balance + amount - fee |
- fee; } |
Когда компилятор обрабатывает тело этой функции, он ищет соответствие между именем fee и именем, локальным для функции. Это имя второго параметра функции. Хотя класс CheckingAccount содержит элемент данных fee, его скрывает имя параметра функции. Для доступа к элементу данных fee в листинге 13.15 нужно использовать операцию области действия.
Применение наследования для развития программы
Часто хороший способ справиться с подобной эволюцией программы — избе жать проблем и методов их устранения. Трудности с международными электрон ными платежами в листингах 13.14 и 13.15 возникают из-за попытки изменить суш,ествуюш,ий программный код (классы Account и CheckingAccount), адаптиро вав его к новым условиям.
С точки зрения традиционного программирования это естественное мышление. Объектно-ориентированное программирование, поддерживаемое С+ +, предпо лагает изменение традиционного мышления. Вместо поиска способов изменения суш1ествуюи;его программного кода можно рассмотреть способы наследования из имеюш,ихся классов для поддержки новых требований.
Здесь нужно уточнить: речь идет о новом способе мышления при написании программы. Применение наследования означает разработку нового программного кода вместо модификации суш^ествующего. Те, кому приходилось вносить измене ния в суш,ествуюш,ие программы, знают, что это два совершенно разных подхода. C-f+ предлагает такой способ для решения проблемы с международными плате жами: оставить в покое написанные 200 страниц исходного кода, "заморозить" классы Account и CheckingAccount и ввести для поддержки нового клиента еще один производный класс:
class InternationalAccount |
: public |
CheckingAccount { |
/ / здорово! |
|||
public: |
|
|
|
|
|
|
InternationalAccount(double |
initBalance) |
|
|
|||
{ balance = initBalance; |
} |
|
|
|
|
|
void deposit(double |
amount, |
double |
fee) |
/ / |
скрывает базовый метод |
|
{ balance = balance |
+ amount |
- fee |
- CheckingAccount::fee; } |
|||
} ; |
|
|
|
|
|
|
582 |
Часть III • Программирование с агрегированием и наследованием |
Данное решение показано в листинге 13.16. Классы Account и CheckingAccount здесь те же, что в листинге 13.14. В еще одном производном классе InternationalAccount не введены дополнительные элементы данных и введена только одна функция-член depositO, отвечающая новым требованиям клиента. Поскольку объекты, являющиеся получателями сообщений depositO с разным числом пара
метров, принадлежат к разным классам, вопрос сокры тия или перегрузки здесь не возникает. Объект a1 получает сообщение с одним параметром, и компилятор вызывает функцию базового класса. Объект а2 получает сообщение с двумя параметрами, и компилятор вызы вает функцию в производном от CheckingAccount классе InternationalAccount. Результат программы представ лен на рис. 13.13.
Листинг 13.16. Пример улучшенной иерархии классов Account |
|
|
||
#include <iostream> |
|
|
|
|
using namespace std; |
|
|
|
|
class Account { |
|
// базовый класс |
|
|
protected: |
|
|
|
|
double balance; |
|
|
|
|
public: |
|
|
|
|
Account(double initBalance = 0) |
|
|
|
|
{ balance = initBalance; } |
// наследуется |
без |
изменений |
|
double getBalO |
|
|||
{ return balance; } |
amount) |
// переопределяется |
в производном классе |
|
void withdraw(double |
||||
{ if (balance > amount) |
|
|
|
|
balance -=amount; } |
// наследуется |
без |
изменений |
|
void deposit(double |
amount) |
{balance +=amount; }
}:
class CheckingAccount |
public Account { |
// производный |
класс |
||
protected: |
|
|
|
|
|
double fee; |
|
|
|
|
|
public: |
|
|
|
|
|
CheckingAccount(double initBalance = 0) |
|
|
|
||
{ balance = initBalance; |
fee = 0.2; } |
// скрывает метод базового класса |
|||
void withdraw(double |
amount) |
||||
{ if (balance > amount) |
|
|
|
|
|
balance = balance - amount - fee; } |
|
|
|
||
} ; |
|
|
|
|
|
class InternationalAccount |
: public CheckingAccount |
// здорово! |
|||
public: |
|
|
|
|
|
InternationalAccount(double initBalance) |
|
|
|
||
{ balance = initBalance; } |
|
|
|
||
void deposit(double amount, double fee) |
// скрывает базовый |
метод |
|||
{ balance = balance + amount - fee - CheckingAccount: :fee; |
} |
|
|||
} |
|
|
|
|
|
int mainO |
|
|
// скрывает базовый |
метод |
|
{ CheckingAccount a1(1000); |
|||||
a1.withdraw(100); |
|
|
// метод производного класса |
||
a1.deposit(200); |
|
|
// метод базового класса |
|
|
Глава 13 • Подобные классы и их интерпретация |
583 |
|
InternationalAccount а2(1000); |
// новый серверный объект |
|
||
a2.deposit(200,5); |
|
// метод производного класса |
|
|
cout « |
" Итоговые балансы\п"; |
|
|
|
cout « |
" Первый объект CheckingAccount: " |
|
|
|
« |
a1.getBal() « |
endl; |
|
|
cout « |
" Второй объект CheckingAccount: " |
|
|
|
« |
a2.getBal() « |
endl; |
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
В этом примере из классов создаются новые производные классы, отвечающие |
|||
|
только за новые функции программы. Применение наследования С+Н |
крае |
угольный камень данного нового подхода к сопровождению ПО: написание нового программного кода вместо модификации существующее го.
На самом деле класс Account нуждается в некоторой модификации. Первый способ состоит в том, чтобы сделать закрытый элемент данных fee защищенным (protected) и дать возможность новому производному классу InternationalAccount обращаться к этим данным. Еще один подход может заключаться в до бавлении к классу CheckingAccount функции-члена, получающей значение этого элемента данных. Клиент (InternationalAccount) мог бы использовать эту функ цию для доступа к данным базового класса. Как уже говорилось выше, предпо чтительнее сделать доступными для одного-двух производных классов несколько элементов данных, чем создавать набор функций, которые можно применять толь ко в этих производных классах.
Еще один способ избежать этой модификации существующего класса CheckingAccount — проявить большую дальновидность на этапе проектирования. Зачем делать элементы данных закрытыми? Согласно принципам объектно-ориентиро ванного программирования, причин здесь несколько:
•Нежелательно создавать в клиенте зависимости от имен элементов данных серверного класса.
• Не хочется усложнять клиента с помощью операций с данными.
•Клиент не должен знать об архитектуре сервера больше, чем необходимо.
•Желательно, чтобы клиент вызывал методы сервера, имена которых поясняют их действия.
•Хотелось бы перенести на серверы детали низкого уровня.
Обратите внимание, что этих целей можно достичь, объявив компоненты сервер ного класса не private, а protected. Ключевое слово protected работает подобно другим модификаторам прав доступа — private и public — относительно разных категорий пользователей классов. Для производных классов, связанных с клас сом отношением наследования, ключевое слово protected работает как public. Оно позволяет производным классам непосредственно обращаться к компонен там базового класса. Для классов-клиентов, не связанных с классом наследова нием, ключевое слово protected функционирует как private. Если предполагается дальнейшее развитие программы через наследование, используйте доступ protected,
ане private.
Со в е т у е м всегда рассматривайте способы использования наследования для развития программы C++. Переносите обязанности с клиентского класса на новые производные классы.
584 |
Часть III * Програм!У1ированив с огрегирование!^ и насдедованиег^ |
Важным вопросом является развитие программы, а не ее первоначальная раз работка. При проектировании программы некоторые ключевые базовые классы могут оказаться на верхнем уровне в иерархии наследования. При большом числе потенциальных пользователей классов приобретают важность проблемы инкапсу ляции, сокрытия информации, переноса обязанностей на серверы. Для этих клю чевых классов желательно использовать модификаторы private. Тем самым вы вынуждаете производные классы применять функции доступа. Для развития про граммы применяйте при создании производных классов классы в нижней части иерархии наследования (хороший пример — класс CheckingAccount). Они имеют небольшое число зависимых производных классов, и вопросы инкапсуляции дан ных, сокрытия информации и переноса обязанностей на серверы становятся не важными.
Вторая модификация — в конструкторе класса CheckingAccount. Чтобы избе жать синтаксической ошибки в клиенте при создании объекта CheckingAccount, добавлено значение параметра по умолчанию. Аналогичные вопросы для состав ных классов обсуждались в главе 12. В следуюш,ем разделе рассматривается со здание производных объектов в C+-f.
Конструкторы и деструкторы для производных классов
При создании объекта производного класса инициализации требуют базовая и производная части. Базовая часть производного класса и его производная часть создаются в строгой последовательности. Вы должны ее знать, чтобы избежать потенциальных синтаксических ошибок и проблем с производительностью.
Вопросы создания объектов при наследовании очень напоминают вопросы со здания объектов при композиции классов. При композиции элементы данных объектов создаются (и вызываются их конструкторы) перед выполнением конст руктора составного класса. Если соответствуюш,ий конструктор не суш^ествует, попытка создания составного объекта может привести к синтаксической ошибке. В противном случае создание составного объекта способно снизить производитель ность программы.
При наследовании классов базовая часть объекта всегда создается (с вызовом конструктора) перед производной частью (и вызовом конструктора производного класса). Если соответствуюш,ий базовый конструктор отсутствует, попытка создать объект производного класса может привести к синтаксической ошибке. При нали чии соответствуюш,его конструктора базового класса создание объекта производ ного класса может снизить производительность программы.
Рассмотрим класс Point из программ в листингах 13.6—13.9 и попробуем усовершенствовать их, добавив обобш,енный конструктор с двумя параметрами.
class |
Point |
{ |
|
|
/ / |
базовый класс |
int |
X, у; |
|
|
|
|
|
public: |
|
|
|
|
|
|
Point(int |
x i , |
int yi) |
/ / |
общий конструктор |
||
{ X = x i ; у = y i ; } |
|
|
||||
void |
set |
(int |
x i , int |
yi) |
|
|
{ X = x i ; у = y i ; } |
|
|
||||
void |
get |
(int |
&xp, |
int |
&yp) const |
|
{ |
xp = x; yp = y; |
} |
} ; |
|
Данное усовершенствование обеспечивает для клиента большую гибкость при создании объектов Point. Теперь у клиента есть возможность во время создания объекта задавать координаты точек. Это лучше, чем задавать неинициализиро ванный объект и позднее инициализировать его с помош^ью вызова функции-члена set().
Глава 13 • Подобные классы и их интерпретация |
585 |
Что касается класса VisiblePoint из листинга 13.6, то это изменение в базо вом классе не требует никакой настройки. На производный класс оно не влияет.
class |
VisiblePoint : public Point { |
/ / наследование public |
int |
visible; |
|
public: |
|
|
void showO |
|
{visible = 1; } void hideO
{visible = 0; }
void |
retrieve(int |
&xp, |
int |
&yp, int &vp) const |
|
{ |
get(xp,yp); |
|
|
/ / |
доступен метод базового класса |
|
vp = visible; |
} } |
; |
/ / |
доступны данные производного класса |
Изменения затрагивают клиента производного класса VisiblePoint. Теперь этот фрагмент содержит синтаксические ошибки.
int mainO |
|
|
|
|
|
|
||
{ VisiblePoint |
b; int x, y, |
z; |
/ / |
определение объекта производного |
||||
|
|
|
|
|
|
/ / |
класса: ошибка |
|
b.set(20,40); |
b.showO; |
/ / |
функции public |
базового и производного классов |
||||
Ь.retrieve(x,у,z); |
|
/ / вызов функции производного класса |
||||||
cout |
« |
" |
Координаты точки: х= " « х « |
" у=" « у « endl; |
||||
cout |
« |
" |
Видимость точки: v i s i b l e = " « z « |
endl; |
||||
return |
0; |
} |
|
|
|
|
|
Как и в случае любого объекта, память для элементов данных производного класса (в данном случае — для элемента данных visible) выделяется перед вы полнением тела конструктора производного класса. Между тем до распределения памяти для данных, описанных в производном классе, создается базовая часть производного объекта. Выделяется память для элементов данных (х и у класса Point) и вызывается конструктор базового класса.
Никакие параметры не передаются конструктору базового класса, поэтому используется конструктор по умолчанию. Так как базовый класс Point преду сматривает конструктор, отличный от применяемого по умолчанию, подставляе мый системой конструктор не вызывается.
Следовательно, попытка создания объекта производного класса дает синтак сическую ошибку — вызов несуществующей функции. Обратите внимание, что клиент, теперь содержащий ошибку, превосходно работает в программах из лис тингов 13.6—13.9.
VisiblePoint b; |
/ / нет синтаксической ошибки в предыдущих версиях |
Добавление конструктора в класс Point делает эту строку синтаксически не корректной. Как уже говорилось, в традиционных языках добавление нового про граммного кода может нарушить работу существующего, но никогда не сделает его синтаксически некорректным. Такая связь между разными частями программы свойственна именно программированию на С+Н-.
Избавиться от проблемы, конечно, можно. В базовом классе нужно исполь зовать конструктор по умолчанию, подставляемый системой или определяемый программистом. Данный конструктор вызывается после распределения памяти для объектов в базовой части производного класса.
class |
Point |
{ |
/ / |
базовый класс |
int |
X, у; |
|
|
|
public: |
|
|
|
|
PointO |
|
|
|
|
{ |
X = 0; |
у = 0; } |
/ / |
теперь клиент в порядке |
Глава 13 • Подобные классы и их интерпретация |
587 |
Один из способов представить вызов функции set() в конструкторе VisiblePoint состоит в том, что компилятор сначала пытается найти локальное соответ ствие в области действия конструктора, затем — в области действия класса VisiblePoint, а потом в базовом классе Point. Можно сказать также, что вызов функции set О принадлежит базовому классу. Поскольку объект производного класса "является видом" объекта базового класса, для функции set() не требует ся целевой объект, так как получателем сообщения является объект производного класса (точнее, базовая часть).
Третий способ представить данный вызов — пожалеть читателя программы и предположить, что писавший ее программист был заинтересован в скорейшем завершении своей работы, а не в том, чтобы сделать программу понятной. При написании программы ее автор знал, к какому классу принадлежит функция set(). Тем не менее читателю предоставлена возможность выбора "способов представления функции", т. е. ему надо догадаться, какая функция имеется в виду. Следовательно, клиент не разрабатывался согласно принципам объектно-ориен тированного программирования. В соответствии с данными принципами, клиент (конструктор VisiblePoint) должен быть написан так, чтобы имена в вызовах функций поясняли действия. C + + поддерживает данный подход и допускает при менение операции для области действия. Тем самым идеи разработчика програм мы передаются ее читателям.
class |
VisiblePoint |
: public |
Point |
{ |
|
|
|
int |
visible; |
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
VisiblePoint(int |
x i , |
int |
y i , int |
view) |
/ / |
параметры для данных |
|
{ P o i n t : : s e t ( x i , y i ) ; |
|
|
/ / |
передача знаний читателям |
|||
|
visible = view; |
} |
|
|
|
|
|
|
. . . . } ; |
|
|
|
/ / |
остальная часть класса VisiblePoint |
Мы снова обраилаемся к интуиции программиста. В традиционных языках предлагаются некоторые способы для передачи идей разработчика читателям про граммы, однако C + + сложнее таких языков. В нем одну и ту же программу можно написать многими способами. Следовательно, суш.ествует многообразие способов для выражения идей разработчиков, поэтому в C + + намного важнее передавать
висходном коде данные идеи. Здесь вам должна помогать интуиция.
Со в е т у е м при разработке программы всегда ищите способы передать свои знания программистам, занимающимся клиентской частью
исопровождением программы. C++ позволяет сделать это посредством самого исходного кода, а не комментариев. C++ часто применяют
как традиционный язык, не используя все его возможности.
Использование в конструкторах производных классов списков инициализации
Добавление конструктора к классу VisiblePoint позволяет перенести обязан ности с клиента VisiblePoint в код VisiblePoint. Между тем это не устраняет проблемы лишнего вызова конструктора в базовом классе.
Применяемый по умолчанию конструктор базового класса вызывается для базовой части производного объекта. Он выполняется сразу после выделения памяти для базовой части объекта. Так как значения полей базовой части при выполнении тела конструктора производного класса устанавливаются заново, конструктор базового класса вызывается зря.
Если в базовом классе есть отличный от используемого по умолчанию кон структор, то конструктор производного класса может вызывать его вместо при меняемого по умолчанию конструктора базового класса. Тем самым устраняется лишний вызов функции.
I 588 I |
Часть III. Программирование с агрегированием и наследованием |
Обратите внимание, что конструктор базового класса всегда вызывается меж ду распределением памяти для базовой части и вызовом конструктора производно го класса. Вопрос лишь в том, какой конструктор вызывается — по умолчанию или отличный от используемого по умолчанию.
Для вызова отличного от используемого по умолчанию конструктора с пара метрами C + + поддерживает синтаксис списка инициализации компонентов, кото рый применятся для координации вызовов конструктора в композиции классов.
class |
VisiblePoint |
: public |
Point |
{ |
|
|
|
|
int |
visible; |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
|
\/isiblePoint(int |
x i , |
int |
y i , int |
view) |
: Point(xi,yi) |
/ / список |
||
{ |
visible = view; |
} |
|
/ / |
нет вызова |
set() |
|
|
. . |
. . } |
|
|
|
/ / |
остальная |
часть класса |
VisiblePoint |
Разница между этими двумя формами списка инициализации очень важна. В композиции классов список инициализации содержит имена объектов-компо нентов в виде имен элементов данных класса. При наследовании список инициали зации содержит имя производного объекта-компонента в виде имени базового класса.
Основное сходство между этими двумя формами списка инициализации — в вызове конструктора компонента. В композиции классов он вызывается сразу после распределения памяти для элемента данных. В наследовании классов это происходит немедленно после распределения памяти для базовой части производ ного объекта. Во всех случаях он вызывается перед телом конструктора класса (контейнерного или производного). Если базовая часть производного объекта со держит компоненты других классов или если компоненты составного объекта имеют базовые классы, данная процедура реализуется рекурсивно.
Итак, C + + создает сначала базовую часть производного объекта, затем вызы вает конструктор базового класса, потом задает производную часть класса и вы полняет тело конструктора производного класса.
Параметры в вызове конструктора, следующие за двоеточием, передаются кон структору базового класса. Они могут быть либо параметрами, передаваемыми из клиента конструктору производного класса (как в последнем примере), либо литеральными значениями, или даже вызовами функций. Ограничений здесь нет.
Если компонент базового класса для инициализации объекта производного класса нуждается в конструкторе по умолчанию, последний он вызывается явно или неявно. Например, нужно инициализировать базовую часть объектов класса VisiblePoint "началом координат" экрана. Тогда конструктор VisiblePoint можно записать так:
class |
VisiblePoint |
: public Point |
{ |
|
|
|
int |
visible; |
|
|
|
|
|
public: |
|
|
|
|
|
|
VisiblePoint(int |
view) : Point() |
/ / |
вызов конструктора по умолчанию |
|||
{ |
visible |
= view; } |
/ / |
нет вызова |
set() |
|
|
. . . . } |
; |
|
/ / |
остальная |
часть класса VisiblePoint |
С другой стороны, нет необходимости вызывать конструктор базового класса явно. Даже без списка инициализации компилятор автоматически вызывает ис пользуемый по умолчанию конструктор базового класса.
class |
VisiblePoint |
: public |
Point { |
|
|
|
int |
visible; |
|
|
|
|
|
public: |
|
|
|
|
|
|
VisiblePoint(int |
view) |
/ / |
неявный вызов конструктора по умолчанию |
|||
{ |
visible |
= view; } |
/ / |
нет вызова |
set() |
|
. |
. . . } |
; |
|
/ / |
остальная |
часть класса VisiblePoint |
Глава 13 « Подобные классы и их интерпретация |
589 |
При создании объекта производного класса эти две версии конструктора про изводного класса проходят одинаковые этапы: распределяется память для базовой части объекта, вызывается используемый по умолчанию конструктор базового класса, распределяется память для производной части объекта, затем вызывается конструктор преобразования производного класса.
Можно комбинировать оба списка таким образом, чтобы элементы данных производного класса инициализировались с помондью синтаксиса инициализации компонента.
class |
VisiblePoint |
: public |
Point |
{ |
|
|
int |
visible; |
|
|
|
|
|
public: |
|
|
|
|
|
|
VisiblePoint(int |
x i , int |
y i , int |
view) |
|
|
|
: visible(view), Point(xi,yi) |
/ / |
что вызывается сначала? |
||||
{ |
} |
|
|
/ / |
популярная |
идиома C++ |
. . |
. . } ; |
|
|
/ / |
остальная |
часть класса VisiblePoint |
Вспомним, что элементы данных всегда создаются в порядке их следования в спецификации класса. Для производного класса базовая часть спецификации не явно является первой и предшествует спецификации компонентов производного класса. Несмотря на то, что она выглядит как приведенный выше список инициа лизации, конструктор базового класса вызывается первым, лишь затем инициали зируются элементы данных производного класса. Тело конструктора производного класса всегда выполняется первым.
Классы Derived без конструктора встречаются редко. Кроме того, не использу ется список инициализации.
В данном примере тело конструктора производного класса является пустым. Инициализация всех элементов данных в списке инициализации конструктора не дает никаких преимуш,еств, но применяется очень часто. По каким-то причинам многие программисты испытывают чувство глубокого удовлетворения, если в теле конструктора производного класса пусто.
Короче говоря, нет необходимости использовать список инициализации в ар хитектуре конструкторов производного класса, если имеют место следуюш,ие два обстоятельства:
1.Базовый класс не содержит конструкторов (при создании базовой части производного объекта вызывается подставляемый системой конструктор, который применяется по умолчанию).
2 . Базовый класс включает в себя определяемый программистом конструктор (он вызывается при создании базовой части производного объекта) и конструкторы производного класса (если они имеются), не изменяюш,ие состояния базовой части производного объекта относительно того, что уже сделано конструктором по умолчанию.
Если базовый класс содержит отличный от используемого по умолчанию кон структор, нужно различать две ситуации:
1. Базовый класс не имеет конструктора по умолчанию.
Тогда конструкторы производного класса для вызова отличных от применяемых по умолчанию конструкторов базового класса должны использовать синтаксис списков инициализации.
Тем самым предотвращаются синтаксические ошибки в определении объекта производного класса.
2 . Базовый класс также содержит определяемый программистом конструктор, который вызывается по умолчанию. Конструкторам производного класса не нужно использовать списки инициализации. Сначала вызывается применяемый по умолчанию конструктор Base,