
Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdf832 |
Часть IV * Расширенное использование C*f^ |
constValue значению nonConstValue, которое неявляется константой. 1ип попConstValue должен быть именно TypeName, тип constValue — const TypeName.
Рассмотрим следующий пример. Поскольку переменная d определяется как const, обычный указатель не может на нее указывать.
const double d -42; |
// ошибка: чтобы предотвратить *pd = 21 |
double *pd = &d; |
Указатель на значение-константу может указывать на переменную d, но он
не меняет свое значение. |
|
const double d = 42; |
// ok, HO неочень практично |
const double *pd = &d; |
|
*pd =21; |
// синтаксическая ошибка: указатель на const |
Операция const_cast прибегает куловке. Она удаляет требование константы и открывает возможностьдля изменения значения, которое определяется какconst.
const double d = 42; |
|
|
|
double *pd = const_cast<double*>(&d); |
/ / |
удалить |
const |
*pd =21; |
/ / |
теперь |
все ok |
cout « "The answer is " << *pd « endl; |
/ / |
выводит 21 |
Это фокус, который невозможно сделать даже для приведения стандартных типов С. Единственной ситуацией, где это необходимо, является сопровождение программы, где переменная была определена как const в соответствии с новыми условиями. Вместо изменения существующего определения можно добавить новый код, где переменная меняется с использованием операции const_cast.
Использование операции const_cast удаляет защиту. Указатель не должен быть указателем на константу. Он может быть разыменован при изменении объекта, на который он указывает. Рассмотрим класс Account.
class Account { |
|
|
// базовый класс иерархии |
|
protected: |
|
|
// защищенные данные |
|
double balance; |
|
|
||
int |
pin; |
|
|
// идентификационный номер |
char owner[40]; |
|
|
|
|
public: |
|
int id, double bal) |
// общее |
|
Account(const char* name |
||||
{ |
strcpy(owner, |
name); |
|
// инициализация полей данных |
balance = bal; pin = id; }} |
|
|||
operator double |
() const |
// общий для обоих счетов |
{return balance; } operator int () const
{return pin; }
operator const char* () const |
|
{ return owner; } |
|
void operator -= (double amount) |
|
{ balance -=amount; } |
|
void operator += (double amount) |
// безусловное приращение |
{ balance +=amount; } |
|
} ; |
|
При попытке вызова функции-члена, неконстанты (например, operator+=()) в объекте const класса Account компилятор отбросит этот код.
const Account alC'Jones", 1122, 5000. 0); |
// создать объект |
a1 += 1000.0; |
// синтаксическая ошибка |
834 Чость IV • Расширенное использование C^-f
Операция dynamic^cast
Операция dynamic.cast является элементом набора компонентов C+ + , кото рые поддерживают информацию о типах в процессе исполнения (run-time-type information — RTTI). Остальными элементами этого набора компонентов являют ся операция typeid и структура type_info.
Операция dynamic_cast применяется для преобразования указателей (или ссылок) базового класса в указатели (или ссылки) одного из производных классов. Операция static_Gast (или стандартное приведение) также может использовать ся, но программа должна знать тип объекта, чтобы быть уверенной в том, что она преобразует указатель в соответствующий класс.
Операция dynamic_cast использует тот же синтаксис, что и другие операции приведения. Аргумент-указатель (или ссылка) заключается в круглые скобки, а тип назначения, в который преобразуется аргумент, указывается в угловых скобках. Если аргумент-указатель действительно принадлежит запрошенному типу назначения, операция возвращает аргумент-указатель на объект без изменений. Кроме того, она отправляет указатель на объект, если аргумент-указатель ссы лается на объект класса, порожденного из типа назначения. В противном случае она возвращает нуль, и программа может проверить значение.
Чтобы этот метод работал, иерархия классов должна содержать как виртуаль ные, так и не виртуальные функции. Он не работает для наследования без вир туальных функций.
Рассмотрим упрощенный класс Account, который содержит виртуальную функ цию displayO. Она отображает содержимое объекта Account.
class |
Account { |
|
|
|
|
/ / |
базовый класс иерархии |
|||
protected: |
|
|
|
|
|
|
|
|
||
double balance; |
|
|
|
|
/ / |
защищенные данные |
||||
int |
pin; |
|
|
|
|
|
|
/ / |
идентификационный номер |
|
char |
owner[40]; |
|
|
|
|
|
|
|||
public: |
|
|
|
|
|
|
|
|
||
Account(const |
char* |
name, |
int |
id, double |
bal) |
/ / общий |
||||
{ |
strcpvCowner, |
name); |
|
|
/ / |
инициализация полей данных |
||||
|
balance |
= bal; |
pin |
= id;} |
|
|
|
|||
v i r t u a l void |
displayO |
|
|
/ / |
виртуальная функция для RTTI |
|||||
{ c o u t . s e t f ( i o s : : f i x e d , i o s : : f l o a t f i e l d ) ; |
cout.precision(2); |
|||||||||
|
cout «setw(6) |
« |
pin « |
setw(20) « balance |
|
|||||
|
|
« |
" " |
« |
owner « e n d l ; |
} |
|
|
||
void |
operator |
-= |
(double amount) |
|
|
|||||
{ balance -= amount; |
} |
|
|
|
|
|||||
void operator += (double amount) |
|
|
||||||||
{ |
balance |
+= amount; |
} |
|
|
/ / |
безусловное приращение |
Производный класс SavingsAccount добавляет метод paylnterestO, являю щийся дополнительным, и переопределяет базовый метод display() таким обра зом, что отображается дополнительный элемент данных interest.
class SavingsAccount |
: public |
Account { |
|
|
double rate, |
interest; |
/ / |
накопленные проценты |
|
public: |
|
|
|
|
SavingsAccount(const |
char* |
name, int id, double bal) |
|
|
: Account(name, |
id, |
bal), |
rate (6.0), interest(O) { |
} |
void paylnterestO |
|
|
/ / |
выплата раз в месяц |
{double pay = balance * rate / 12 / 100; balance += pay; interest += pay; }
Глава 18 # Программирование с обработкой исключительных ситуаций
v i r t u a l |
void displayO |
|
|
|
|
|
||
{ c o u t . s e t f ( i o s : : f i x e d , i o s : : f l o a t f i e l d ) ; |
cout |
. precision(2); |
||||||
cout |
«setw(6) |
« |
pin |
« |
setw(8) « |
interest |
« setw(12) |
|
|
« balance |
« |
" " |
« |
owner « |
endl; |
} |
|
} ;
Здесь определяются объекты базового и производного классов. Кроме того, определяется указатель Account и используется операция dynamic_cast для его установки сначала на базовый объект, а затем на производный объект. При ис пользовании операции dynamic_cast следует проверить, является ли указатель нулевым. Если да, то ответ на вопрос отрицательный. Если указатель не нулевой, ответ утвердительный. Объект, который обозначает указатель, может использо ваться как объект класса, определенного в операции dynamic_cast.
Account a1 ( "Jones", 1122,5000) ; |
/ / |
создать объекты |
||||
SavingsAccount а2 ( |
"Smith",1133,3000); |
|
|
|||
Account |
*pa = dynamic_cast<Account *>(&a1); |
/ / o k |
||||
i f |
(pa |
== 0) |
|
|
|
|
|
cout |
« |
"Null pointer\n"; |
|
|
|
else |
|
|
|
|
|
|
|
pa->display(); |
|
/ / |
Jones |
||
pa = dynamic_cast<SavingsAccount *>(&a2); |
/ / |
ok |
||||
i f |
(pa == 0) |
cout « |
"Null pointer\n"; |
|
|
|
else |
|
|
|
|
|
|
|
pa->display(); |
|
/ / |
Smith |
В этом примере ответы на вопрос не очень важны, поскольку указатель, исполь зуемый как цель присваивания, является базовым. Первое приведение возвра щает указатель на объект a1, а второе приведение— на объект а2, который преобразуется в базовый указатель. Поскольку функция displayO является по лиморфной, она отображает данные в первом случае в базовом формате и во втором случае в производном формате. В этом фрагменте программы демонстри руется поведение операции dynamic_cast.
В следующем фрагменте программы базовый указатель снова используется как цель. Во-первых, он указывает на базовый объект, у которого запрашивается, может ли он выполнить операции Account. Ответ утвердительный, т. е. операция возвращает базовый указатель, ссылающийся на интересующий объект. Тем не менее сообщение displayO, посылаемое через этот указатель, указывает на данные в формате производного класса, потому что функция является виртуаль ной. Объект принадлежит производному классу. Затем проверяется, может ли объект a1 выполнить обязанности объекта SavingsAccount. Ответ отрицательный, это объект базового класса и операция возвращает нуль.
ра = dynamic_cast<Account |
*>(&а2); |
/ / |
ок |
|||
i f |
(pa |
== 0) |
|
|
|
|
|
cout |
« |
"Null pointer\n"; |
|
|
|
else |
|
|
|
|
|
|
|
pa->display(); |
|
/ / |
Smith |
||
pa = dynamic_cast<SavingsAccount *>(&a1); |
/ / |
нулевой |
||||
i f |
(pa == 0) |
cout « "Null |
pointer\n"; |
|
|
|
else |
|
|
|
|
|
|
|
pa->display(); |
|
|
|
Следующий фрагмент программы намного интереснее, поскольку в нем ис пользуется указатель производного класса. Сначала проверяется, может ли объ ект a1 выполнить обязанности производного класса. Как и в предыдущем случае, ответ отрицательный. Совершенно все равно, является ли целью присваивания
836 |
Часть IV # Расширенное использование С*^^ |
базовый указатель (как в предыдуидем случае) или производный указатель (как в данном случае) — нуль всегда нуль. Затем проверяется, может ли объект а2 выполнить обязанности производного класса. Ответ, как и для самого первого фрагмента программы, положительный. В первом фрагменте результат преобра зовывался в базовый указатель, следовательно, мог вызывать только методы Account и виртуальные методы производного класса. Здесь преобразование от сутствует. Целью присваивания является производный указатель. Он может осуществлять доступ к базовым функциям, виртуальным функциям и функциям, определенным в производном классе.
SavingsAccount *psa = clynamic_cast<SavingsAccount |
*>(&a1); |
/ / О |
||||
i f |
(psa |
== 0) |
|
|
|
|
|
cout |
« |
"Null pointer\n"; |
/ / |
нулевой указатель |
|
else |
psa->clisplay(); |
/ / |
без отображения |
|||
|
||||||
psa = dynamic_cast<SavingsAccount *>(&a2); |
/ / |
ok |
|
|||
i f |
(psa |
== 0) |
|
|
|
|
else |
cout |
« |
"Null pointer\n"; |
|
|
|
|
|
|
|
|
|
|
|
{ psa->paylnterest(); |
/ / |
производный метод |
|||
|
psa->display(); } |
/ / |
Smith |
|
||
Понятно, |
что операция clynamic_cast представляет собой |
мощный метод |
проверки, может ли указанный объект выполнить требуемую операцию. Не все компиляторы поддерживают такую возможность. Те, которые поддерживают ее, могут не делать это по умолча«нию. Для использования данной возможности необходимо установить флаги компиляции или опции для явной поддержки возможностей RTTI.
Подобно оператору new, C + + поддерживает другой метод проверки, будет ли успешным приведение типов: генерация исключительной ситуации. Если указа тель не ссылается на объект класса, определенного в операции, то генерируется исключительная ситуация bad_cast. Это важно для ссылок. Указатели C + + мо гут как указывать, так и не указывать на объект, но ссылки C + + делают это всегда. Они не могут иметь нулевое значение. Для ссылок не нужно использо вать dynamic_cast, чтобы показать, что действительно указывает на объект клас са, определенного в приведении. Когда формальное утверждение оказывается неверным, соответствующим является генерация исключительной ситуации.
Операция typeid
Для определения того, к какому классу следует выполнить приведение базово го указателя, используется операция typeid. С помощью операции typeid можно выполнить одно из двух: проверить имя класса параметра или уточнить, относится ли к данному классу объект, на который установлен указатель.
В отличие от операций приведения типа операция typeid работает с парамет ром-объектом, а не с параметром-указателем. Она возвращает ссылку на объект type_inf о библиотечного класса. Реализация этого вызова зависит от компилято ра. Однако среди своих компонентов класса она всегда включает функцию-член name(). Эта функция возвращает символьный массив, т. е. снова зависит от реали зации. Чаще всего это имя класса, к которому принадлежит параметр операции или имя класса с предшествующим зарезервированным словом class.
Некоторые реализации компилятора определяют конструкторы type_inf как закрытые. В таком случае программа C + + не может создать объект класса type_inf 0. Вместо этого она должна послать сообщение в возвращаемом значении оператора typeid.
Глава 18 # Программирование с обработкой исключительных ситуаций |
[ 837 | |
|||
Account a1("Jones",1122,5000); |
// создать объекты |
|
||
SavingsAccount a2("Smith",1133,3000); |
// получить имя класса |
|
||
const char *c1 = typeicl(al). name(); |
|
|||
const char *c2 = typeicl(a2). name(); |
// выводит "class Account" |
|||
cout « |
c1 « |
endl-; |
||
cout « |
c2 « |
endl; |
// выводит "class SavingsAccount" |
Операция typeid не может быть выражением некоторого класса, а также име нем класса, указанным как идентификатор (без кавычек). Это позволяет сравни вать результаты использования операции typeid с именем класса и с объектом. Если операция равенства возвращает true,то объект принадлежит к указанному классу.
if |
(typeid(Account) == typeid(al)) |
//true |
|
if |
cout « |
"a1 is Account\n"; |
// true |
(typeid(SavingsAccount) ==typeid(a2)) |
|||
if |
cout « |
"a2 isSavingsAccount\n" ; |
// false |
(typeid(Account) == typeid(a2)) |
|||
if |
cout « |
"a2 is Account\n"; |
// false |
(typeid(SavingsAccount) == typeid(al)) |
|||
|
cout « |
"a1 is SavingsAccount\n"; |
|
Bo время работы с набором неоднородных объектов, обозначенных базовыми
указателями, указатели, используемые как параметры в typeid, должны быть разыменованы.
ра = &а2;
i f |
(typeid(Account) == typeid(*pa)) |
/ / |
false |
|
|
cout « |
"pa points to Account\n"; |
|
|
i f |
(typeid(SavingsAccount) ^^ typeid(*pa)) |
/ / |
true |
|
|
cout « |
"pa points to SavingsAccount\n" |
|
|
Обратите внимание, что эти операции не сравнивают объекты. Для структур С+Н- операции сравнения не определены. В них не сравниваются указатели. В от личие от других специальных приведений оператор typeid возвращает объект, а не указатель. Он применяет перегруженную операцию сравнения к объекту type_inf, который возвращается операцией typeid.
Операция typeid — мощное средство, которым легко злоупотребить. Исполь зуйте ее как можно реже.
Итоги
Возможности, рассмотренные в данной главе, относительно новые. Не все компиляторы и реализации библиотек их поддерживают. Советуем вам использо вать их с осторожностью.
Для выполнения исключительных ситуаций требуется затратить время и па мять. Это может оказаться важным для некоторых приложений. Важно правильно использовать исключительные ситуации для структуризации и упрощения переда чи управления в приложении.
Может показаться, что исключительные ситуации, которые генерируют значе ния встроенных типов, не слишком полезны. Ведь программы обработки исключи тельных ситуаций не могут отличить значения одинакового типа, сгенерированные в разных местах исходной программы. Проектирование классов для передачи ин формации исключительной ситуации от места обнаружения ошибки к месту ее исправления очень полезная и интересная задача.
838 Часть IV * Расширенное использование С+4-
Многие подтверждают, что использование исключительных ситуаций упрощает передачу управления в приложении и позволяет проектировщику отделить основ ную обработку от запутанных исключительных случаев. Возможно, использование управляющих структур if и switch действительно приводит в замешательство — исходный текст программы становится трудным для понимания. Имейте в виду, что сложная структура программы отражает трудные для понимания задачи. Несо мненным преимуществом использования стандартных управляющих структур для обработки различных случаев является то, что вся программа обработки находит ся в одном месте, она не разделена на части.
При использовании исключительных ситуаций программист, осуществляющий сопровождение, должен выполнять анализ решений, сделанных проектировщиком в отношении того, как разделить обработку на отдельные части. Эти решения часто сложны и имеют произвольный характер. Таким образом усложняется про грамма.
Представляется, что использование исключительных ситуаций оправдано в слу чае, когда программа проектируется таким образом, что ее часть, вьшолняющая исправление ошибок, не располагает информацией о той части программы, кото рая обнаруживает ошибку. Советуем вам использовать исключительные ситуации для передачи необходимой информации от одной части программы к другой. Одна ко помните, что необходимость передачи информации от одной части программы к другой возникает в том случае, когда принято решение о разделении обработки на две отдельные части. Пересмотр такого решения может исключить необходи мость чрезмерной обработки исключительных ситуаций.
Убедитесь, что использование исключительных ситуаций C + + не слишком усложняет работу программиста, осуществляющего сопровождение. Обозначьте все исключительные ситуации, которые каждая функция генерирует или передает от своего сервера. Не трогайте исключительные ситуации, которые функция не сможет сгенерировать. Снабжайте комментариями обработку исключительной си туации в исходном тексте программы и выделяйте документирование. Убедитесь, что каждая программа обработки исключительной ситуации проверена.
Программисты не имеют опыта использования операций приведения типов. Автор использует стандартные приведения С-типа, и кажется, что этого вполне достаточно. Конечно, можно неверно воспользоваться стандартными приведения ми типа. Стандартные приведения типов позволяют осуществлять преобразова ния указателей, которые не имеют смысла, а операция static_cast не позволит это сделать. Но преобразования, не имеющие смысла, обычно не делаются. Если такое преобразование потребуется, операция reinterpret_cast позволит сделать это настолько же легко, насколько допускает стандартное приведение типа.
Тем не менее большинство экспертов соглашаются, что за счет использования операции приведения типов повышается надежность программ. Они хорошо за метны и привлекают внимание программиста, осуществляющего сопровождение. Рекомендуем вам использовать их на практике.
п |
^ ^ / ^ |
IB |
|
|
|
|
олученные уроки |
|
Темы данной главы
^C++ как традиционный язык программирования ь/ C++ как модульный язык
ь/ C++ как объектно-ориентированный язык
^C++ и его конкуренты
•^ Итоги
|
^jfy^ |
Ь1 прошли длинный путь. Стоит признать, что эта книга создава- |
щ^Х |
Шг |
в ласьдолго. Иногда казалось, что последняя глава никогда не будет |
\>^ |
|
^^написана. Однако пришло время завершить свой тяжелый труд. |
Теперь можно оглянуться назад.
В этой главе не изучается новый синтаксис. Вместо этого попытаемся кратко обобш^ить основные характеристики этого великого, замечательного, запутанного языка С+ + . Теперь, когда вы усвоили весь предмет целиком и поняли, как разные компоненты состыковываются друг с другом, можно оценить, как много идей вложено в его проектирование и насколько осмотрительным нужно быть при его использовании.
В предыдуш,их главах были установлены некоторые ограничения в отношении того, что можно, а что нельзя обсуждать, поскольку некоторые веш,и были недо статочно хорошо известны. Теперь это ограничение не имеет силы.
Язык С+4- был создан как язык программной инженерии для построения боль ших компьютерных программ. Он преследовал несколько целей. С одной стороны, СН-+ старался быть языком программирования систем, от которых требуется вы сокая производительность: обеспечивая операции низкого уровня (например, сдвиги и побитовые логические операции) и поддерживая доступ к ресурсам компьютеров (например, регистры, изменчивые типы данных и арифметические операции над указателями). С другой стороны, C + + пытался облегчить разделе ние программ на независимые части, которые могли разрабатываться разными программистами, мало взаимодействуюш,ими друг с другом.
C + + является средством достижения нескольких конфликтуюш,их целей. Он был спроектирован:
•Как удобочитаемый язык высокого уровня (агрегирование данных, передача управления, область действия имен)
•Как язык для острого и гибкого ума (уникальные по краткости записи операторов, лаконичные выражения)