Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

820 Часть iV » Расширенное использование С^4^

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

В листинге 18.7 показана та же программа, что и в листингах 18.1 —18.6. Функ­ ция inverseO генерирует объекты класса ZeroDenom и NegativeDenom. Поскольку эти функции вызывает функция f ractionO, не знаюш,ая способа обработки этих исключительных ситуаций, имейте в виду, что это та функция, которая генерирует исключительные ситуации. Функция f ractionO также обозначает данные исклю­ чительные ситуации. Кроме того, main() должна поместить вызов fractionO в блок try и предоставить две структуры catch, по одной для каждой исключитель­ ной ситуации.

Листинг 18.7. Пример генерации объектов класса вместо встроенных значений

#include

<iostream>

 

#inclucle

<cfloat>

 

using namespace std;

 

class MSG {

 

// внутренние статические данные

static char* data[];

public:

 

 

// общедоступный статический метод

static char* msg(int n)

{ if (n<l

I I n > 5)

// проверка допустимости индекса

 

return data[0];

 

 

else

 

// возвращение допустимой строки

} ;

return data[n]; }

 

 

 

char* MSG::data [] = { "\nBad argument to msg()\n",

"\nZero denominator isnot allowed\n\n"

// хранилище текста

"\nNegative denominator: ",

 

"Enter numerator and positive\n",

 

"denominator (any letter toquit): ",

 

"Value ofthe fraction: "

 

} :

 

 

 

class ZeroDenom {

// данные, передаваемые программе обработки ошибок

char *msg;

public:

 

 

// вызывается оператором throw

ZeroDenom ()

{ msg = MSG::msg(1); }

// вызывается в блоке catch

void print 0 const

{ cout «

msg; }

 

} ;

 

 

 

class NegativeDenom {

// закрытые данные для исключительной ситуации

long val;

 

char* msg;

 

 

public:

 

 

// конструктор преобразования

NegativeDenom(long value)

 

: val(value), msg(MSG::msg(2)) { }

char* getMsgO

const

 

{ return msg; }

// общедоступные методы доступа кданным

long getValO

const

{ return val; }

 

} ;

 

 

 

Глава 18 # Программирование с обработкой исключительных ситуаций

821

inline void inverse(long value, clouble& answer)

 

 

{

throw (ZeroDenom, NegativeDenom)

 

 

answer = (value) ? 1.0/value : DBL_MAX;

 

 

 

if (answer==DBL_MAX)

 

 

 

throw ZeroDenomO;

 

 

 

if (value < 0)

 

 

 

throw NegativeDenom(value); }

 

 

inline void fraction(long numer, long denom, doubles result)

 

{

throw (ZeroDenom, NegativeDenom)

// result = 1.0 / denom

 

inverse (denom, result);

 

 

result = numer * result; }

// result = numer / denom

 

int mainO

 

 

{

while (true)

 

 

 

// числитель/знаменатель

 

 

{ long numer, denom; double ans;

 

 

cout « MSG::msg(3) « MSG::msg(4);

// запрос ввода данных пользователем

 

 

if ((cin » numer » denom) == 0) break;

// ввод данных

 

 

try {

// вычисление ответа

 

 

fraction(numer,denom,ans);

 

 

cout « M S G : :msg(5) « ans «"\n\n"; }

// действительный ответ

 

 

catch (const ZeroDenom& zd)

// нулевой знаменатель

 

 

{ zd.printO ; }

// отрицательное значение

 

 

catch (const NegativeDenom &nd)

 

{ cout « nd.getMsgO « nd.getValO « "\n\n'; }

}

return 0;

}

Использование наследования с исключительными ситуациями

Состояния ошибки в программе могут быть подобными друг другу. Инфор­ мация, которая необходима для исправления ошибки, может иметь такую же структуру. Например, в листинге 18.7 каждая исключительная ситуации передает указатель на символьный массив, который должен быть выведен как сообш,ение об ошибке.

Как часто случается с подобными классами, проектирови;ик может организо­ вать классы исключительных ситуаций программы в иерархии наследования. На­ пример, можно доработать классы ZeroDenom и NegativeDenom так, что класс NegativeDenom порождается из класса ZeroDenom.

class ZeroDenom { protected:

char *msg;

public:

ZeroDenom (char* message) msg(message)

{ }

void print 0 const { cout « msg; }

}

В предыдуш,ей версии этого класса имя символьного массива было жестко за­ программировано в конструкторе класса. В результате клиент этого класса, функ­ ция inverseO в листинге 18.7, не должна была знать, какое сообш^ение отправить

822

Часть IV • Расширенное использование С-^-ь

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

Трудно сказать, какой подход лучше. Как правило, первый подход (реализован­ ный в листинге 18.7) передает обязанности вниз к серверному классу ZeroDenom, а второй подход — вверх клиентам класса. Однако общая схема распределения информации между классами программы может сделать более привлекательным второй подход. Хотелось бы быть уверенным, что это различие запомнилось, что оно понятно и вполне осознается в программе.

class NegativeDenom : public ZeroDenom { long val;

public:

NegativeDenom(char ^message, long value) : ZeroDenom(message), val(value) { }

void print 0 const

{ cout « msg « val « "\n\n"; }

} ;

NegativeDenom был порожден из ZeroDenom. Можно ли породить ZeroDenom из NegativeDenom? В принципе возможно. С практической точки зрения, однако, это не очень хорошая идея. Класс NegativeDenom содержит больше компонентов в наборе данных, чем класс ZeroDenom.

Данные в базовом классе ZeroDenom объявляются защищенными, а не закры­ тыми, так что порожденный класс NegativeDenom способен осуществить доступ

к базовым данным. Если же данные ZeroDenom были бы закрытыми, то методы

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

class NegativeDenom

public ZeroDenom

 

long val;

 

 

public:

 

 

NegativeDenom(char ^message, long value)

}

: ZeroDenom(message), val(value) {

void print 0

const

// вызов базового метода

{ ZeroDenom::print();

cout « val « "\n\n"; }

 

}

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

ив производном классе, содержат общие элементы, было бы замечательно под­ черкнуть этот факт в программе производного класса. Для этого надо вызвать метод базового класса в соответствующем методе производного класса. С другой стороны, добавление к базовому классу методов доступа, которые используются только в производном классе, это напрасная трата времени.

Когда классы исключительных ситуаций связаны наследованием, обозначение исключительной ситуации и ее генерация объектов такие же, как и для несвязан­ ных классов исключительной ситуации. Тем не менее отслеживание исключитель­ ных ситуаций может вызывать дополнительные проблемы, если только не будет уделено достаточное внимание взаимоотношениям между классами. В листин­ ге 18.8 показана программа из листинга 18.7, измененная таким образом, что класс NegativeDenom является производным от класса ZeroDenom.

Функции inverseO и f raction() требуют такие же исключительные ситуации, как в листинге 18.7. Однако именно функция inverse(), а не классы исключитель­ ных ситуаций ZeroDenom и NegativeDenom, знает, какое сообщение генерируется для каждой исключительной ситуации.

Глава 18 ^ Програмл^ировоние с обработкой ыскйЮчттвАьиык ситуаций

823

Enter

numerator

and positive

 

 

 

Пример вывода для этой программы приве­

11

0

 

ден на рис. 18.5. В нем использована прибли­

denominator (any letter toquit):

 

зительно та же последовательность входных

Zero

denominator

isnot allowed

 

 

 

 

 

 

данных, что и для предыдущего варианта про­

Enter

numerator

and positive

11

-42

 

граммы.

 

denominator (any letter toquit):

 

Как можно видеть, вывод программы неве­

Negative denominator: Enter numerator and positive

рен. Когда знаменатель отрицательный, про­

грамма выводит соответствуюилее

сообщение

denominator (any letter toquit): -11 44

 

Value ofthe fraction:

-0.25

 

 

 

об ошибке, но не отображает значение отрица­

Enter

numerator and positive

 

 

 

тельного знаменателя. Вместо этого она пере­

 

 

 

ходит к запросу следующего набора входных

denominator (any letter toquit): exit

 

 

 

 

 

 

 

 

 

данных. Что сделано неправильно?

 

Рис. 18.5. Вывод для

программы

 

Вспомним, что исключительную

ситуацию

 

из листинга

18.8

 

 

 

можно сгенерировать с двумя контекстами:

 

 

 

 

 

 

 

из блока try и вне любого блока try. Когда

 

 

исключительная ситуация генерируется вне блока try, функция завершает свое

 

 

выполнение немедленно и проверка повторяется в вызывающей программе. Вы­

 

 

зов функции, которая

генерирует исключительную ситуацию, может быть либо

в рамках блока try, либо вне блоков try.

Например, функция inver.se() генерирует свои исключительные ситуации вне какого-либо блока try. Когда генерируется любая из этих исключительных ситуа­ ций, выполнение inverseO немедленно прекращается и управление передается вызывающей ее программе fraction(). В программе fractionO вызов inverseO, которая генерирует исключительную ситуацию, находится вне любого блока try. Именно поэтому fractionO также немедленно завершается и управление пере­ дается main О.

Листинг 18.8. Пример использования классов исключительных ситуаций, связанных наследованием

#inclucle

<iostream>

 

 

# include

<cfloat>

 

 

using namespace std;

 

 

class MSG {

// внутренние статические данные

static char* data [];

 

 

public-

 

// общедоступный статический метод

static char* msg(int n)

{ if (n < 1 I I n > 5)

// проверка допустимости

индекса

 

return data[0];

 

 

else

// возвращение допустимой

строки

 

return data[n]; }

char* MSG:: data [] = { "\nBad argument tomsg()\n",

 

"\nZero denominator isnot allowed\n\n",

// область хранения текста

"\nNegative denominator: ",

 

 

"Enter numerator and positive\n",

"denominator (any letter toquit): ", "Value ofthe fraction: "

} :

class ZeroDenom { protected:

char *msg; public:

ZeroDenom (char* message) : msg(message)

{ }

824

 

 

Часть IV # Расширенное использование C'f+

 

 

 

void print О

const

 

 

 

 

}

 

{ cout «

msg; }

 

 

 

 

 

 

 

 

 

 

 

 

 

class NegativeDenom : public ZeroDenom {

 

 

 

 

long val;

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

 

NegativeDenom(char ^message, long value)

 

 

 

 

: ZeroDenom(message), val(value) { }

 

 

 

 

void print 0 const

 

 

 

 

 

 

{ cout «

msg « val « "\n\n"; }

 

 

i n l i ne void

inverse(long value,

double& answer)

 

 

 

 

throw

(ZeroDenom,

NegativeDenom)

 

 

{

answer = (value)

? 1.0/value

: DBL.MAX;

 

 

 

i f

(answer==DBL_MAX)

 

 

 

 

 

 

throw

ZeroDenom(MSG::msg(l));

 

 

 

i f

(value

< 0)

 

 

 

 

 

 

 

throw

NegativeDenom(MSG::msg(2) , value); }

 

 

inline void fraction (long numer, long denom, double& result)

 

 

 

throw

(ZeroDenom,

NegativeDenom)

 

 

{

inverse (denom,

result);

 

/ /

result

= 1.0 / denom

 

result = numer *

result;

}

/ /

result

= numer / denom

int mainO

 

 

 

 

 

{

 

 

 

 

 

 

 

while

(true)

 

 

 

 

{ long numer, denom; double ans;

cout

«

MSG::msg(3)

« MSG::msg(4);

i f

((cin

»

numer »

denom)

== 0) break;

try {

 

 

 

 

 

 

 

 

fraction(numer,denom,ans);

 

 

cout

«

MSG: :msg(5) «

ans « " \ n \ n " ; }

catch

(const

ZeroDenom &zd)

 

{

zd . printO;

}

 

 

catch

(const

NegativeDenom &nd)

{

nd . printO;

} }

 

 

return

0;

 

 

 

 

 

}

 

 

 

 

 

 

 

/ /

числитель/знаменатель

/ /

запрос пользователю на ввод данных

/ /

ввод данных

/ /

вычисление

ответа

/ /

допустимый

ответ

/ /

нулевой знаменатель

/ /

отрицательное значение

/ /

конец цикла

 

Когда исключение генерируется внутри блока try, управление передается в конец блока, который содержит оператор throw. После блока try должны размещаться один или несколько структур catch. Параметры этих структур catch проверяются друг за другом. Если совпадение не обнаруживается, то ситуация рассматривает­ ся так же, как если бы исключительная ситуация была сгенерирована вне блока try. Выполнение функции немедленно прекращается, а управление передается вызвавшей ее программе. Если находится совпадение, поиск прекращается и управление передается совпадающей структуре catch. После завершения вы­ полнения этой структуры catch все последующие структуры catch пропускаются. Выполнение продолжается с помощью обработки оператора, который располага­ ется после структур catch.

Типы совпадают, если они одинаковы. Они также совпадают, если производ­ ный объект порождается из "пойманного" типа или если сгенерированный объект ссылается на объект производного класса, тогда как "пойманный" тип указывает

Глава 18 • Программирование с обработкой исключительных ситуаций

825

на объект базового класса. Вспомните правило: объект производного класса мо­ жет использоваться там, где ожидается объект базового класса (оно подробно рассматривается в главе 15).

В листинге 18.8 при обработке исключительной ситуации NegativeDenom выпол­ нение функций inverseO и fractionO прекраидается, поскольку они не содержат блок try. Когда заканчивается выполнение функции fractionO, она генерирует исключительную ситуацию (полученную из inverseO) для функции main(). По­ скольку mainO вызывает fractionO в блоке try, структуры catch проверяются друг за другом. Вначале проверяется "ловушка" с параметром ZeroDenom. Поскольку объект NegativeDenom, сгенерированный fractionO, может использоваться там, где ожидается объект ZeroDenom, поиск прекращается и выполняется блок-" ловушка" ZeroDenom. Он отправляетсообш,ение базового класса ZeroDenom::print() его объекту-аргументу. Он выводит только сообидение, а не значение, которое со­ держит объект NegativeDenom, но ZeroDenom: :print() не знает, как выполнить вывод, поскольку значение является элементом данных производного класса.

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

Следовательно, не следует помеш^ать первым блок-"ловушку" для базового класса. Необходимо разместить его последним. Вот как будет выглядеть функция mainO из листинга 18.8 после исключения этой проблемы.

i nt mainO

{while (true)

{long numer, denom; double ans;

cout «

MSG::msg(3)

«

MSG::msg(4);

/ /

приглашение ввода данных

 

 

 

 

denom) == 0) break

/ /

пользователем

i f

((cin

»

numer »

/ /

ввод данных

t r y

{

 

 

 

 

 

 

 

fraction(numer,denom,ans);

/ /

вычисление ответа

 

cout

«

MSG: :msg(5)

« ans « " \ n \ n " ; }

/ /

допустимый ответ

catch

(const

NegativeDenom &nd)

/ /

производный

класс

{ nd.printO

; }

 

 

 

catch

(const

ZeroDenom &zd)

/ /

базовый класс

{ zd . printO;

} }

/ /

конец цикла

 

return 0;

}

 

 

 

 

Стандартная библиотека исключительных ситуаций

Стандартная библиотека C+-I- определяет несколько классов стандартных иск­ лючительных ситуаций, организованных с соблюдением иерархии наследования. Наиболее важными классами являются класс exception (все буквы в нижнем регистре), который представляет собой базовый класс в иерархии, и bad_alloc, который порождается из класса exception.

Класс exception определяется в заголовочном файле <exception>, <except.h> или <exception. h>. Класс exception включает виртуальную функцию whatO, ко­ торая возвращает символьный указатель, подобно методу getMsgO в классе из листинга 18.7, приведенного ранее. Информационное наполнение строки не опре­ делено, но можно спроектировать класс, являюил,ийся наследником класса. В этом классе можно переопределить what().

class NegativeDenom

{

long val;

/ / закрытые данные для информации exception

char* msg;

 

826

Расштрвииов использование С^^-^

public:

 

 

/ / конструктор преобразования

 

NegativeDenom(long value)

 

 

: val(value), msg(MSG: :nisg(2)) { }

 

const

char*

whatO const

/ / может возвратить произвольную строку

 

{

return

msg; }

 

 

long

getVal

() const

 

 

{

return

val; }

 

}

;-

 

 

 

Класс bacl_alloc определяется в заголовочном файле <new> или <new.h>. Его объект генерируется, когда оператор new не может выделить требуемый объем памяти из динамически распределяемой области. Пока не все компиляторы под­ держивают эту исключительную ситуацию. Приведем небольшой пример, в котором строится длинный связанный список блоков памяти. Он использует исключитель­ ную ситуацию baci_alloc. Кроме того, он проверяет, возвраш,ает ли оператор new пустой указатель.

#inclucie

<iostream>

 

 

 

 

 

 

/ /

включая файлы

#include

<exception>

 

 

 

 

 

 

 

 

#inclucle

<new>

 

 

 

 

 

 

 

 

 

using

namespace std;

 

 

 

 

 

 

 

 

struct

Block

 

 

 

 

 

 

 

 

 

 

{ char

a[1000];

 

 

 

 

 

 

 

/ /

блок памяти

Block*

next;

 

 

 

 

 

 

 

 

/ /

присоединить перед ptr

Block

(Block*

ptr)

 

 

 

 

 

 

{

next

= ptr;

 

} }

 

 

 

 

 

 

 

 

int mainO

 

О, *p; int cnt = 1;

 

 

 

{ Block *list

 

 

 

 

while (true)

 

 

 

 

 

 

/ /

перейти,

пока он не завершится аварийно

{

try {

 

 

 

 

 

}

 

 

 

 

 

 

p = newBlock(list)

 

 

 

/ /

это не выполнится

 

catch (bad_alloc &bad)

«

endl;

 

 

 

 

{

cout «

 

bad.whatO

 

/ /

сообщение при исправлении

 

 

exit(O); }

 

 

 

 

 

 

 

 

 

if (p - 0)

 

 

 

 

 

 

 

/ /

сообщение при исправлении

 

 

{

cout

«

"Out

of

memory\n\n";

exit(0

 

 

 

l i s t

= p;

 

 

 

 

 

 

 

 

/ /

успех : верх списка

 

i f

(++cnt%100 == 0)

 

 

 

 

 

 

 

 

 

cout «

"Block

#"

«

cnt «

endl; }

/ /

выполнение отслеживания

while (p != 0)

 

 

 

 

 

 

 

 

 

{

p = p->next;

delete l i s t ;

l i s t

= p;

}

/ /

освобождение памяти

return

0; }

 

 

 

 

 

 

 

 

 

 

Механизм исключительных ситуаций не поддерживает асинхронные исключи­ тельные ситуации, например прерывания. Он обрабатывает синхронные исклю­ чительные ситуации, возникаюш,ие в процессе последовательного выполнения, например переполнение, ошибки выхода за пределы области, ошибки выделения ресурсов и неверные входные данные. Исключительные ситуации не должны ис­ пользоваться для состояний, которые являются обычными для потока выполняе­ мых операций, например завершение выполнения одного этапа обычной обработки (окончание списка цикла) и начало другого.

Использование исключительных ситуаций языка С + 4- имеет два главных преимуш,ества. Во-первых, они обеспечивают обмен информацией между местом об­ наружения ошибки и местом, где можно ее исправить. Во-вторых, возврат стека в исходное состояние в процессе завершения вызванной функции и передача

Глава 18 • Программирование с обработкой исключительных ситуаций

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

Операции приведения типов

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

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

Однако операции приведения типов представляют собой набор интересных идей из области программной инженерии. Рекомендуем вам познакомиться с ними. Стоит ли их использовать на практике — решайте сами.

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

Чтобы помочь программистам справиться с этой ситуацией, C++ вводит не­ сколько дополнительных операций приведения. Область их действия шире, чем у стандартных операций приведения типов. Фактически это является одним из их преимуществ, поскольку операции приведения легче заметить в исходной программе, чем стандартные операции приведения типов.

Операция static^cast

Операция static_cast может применяться везде, где работает стандартное

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

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

valueOflargetlype = static_cast<TargetType>(valueOfSourceType);

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

Использование такого приведения типов не ограничивается только присваива­ нием. Оно может применяться в любом месте, где может использоваться значение типа назначения TargetType. Вот простой пример.

double d; int i = 20;

 

d = static_cast<double>(i) ;

/ / ok: d равно 20.0

828

Часть IV ^ Расширенное использование С-^+

Лучше ли это, чем старый и надежный друг для приведения типов — double? Это совершенно одинаковые веш,и.

double d; int i = 20;

/ / o k : d равно 20.0

d = double(i) ;

Рассмотрим сложный пример. Класс Account предусматривает несколько опе­ раций преобразования, которые извлекают значения своих компонентов. Для простоты используется массив фиксированного размера для имени владельца.

class Account {

// базовый класс иерархии

protected:

// защищенные данные

double balance;

int pin;

// идентификационный номер

char owner[40];

 

public:

 

Account(const char* name, int id,double bal)

{

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; }

} ;

 

 

Как уже говорилось в главе 15, эти функции перегруженных операций могут вызываться сиспользованием того же синтаксиса,что и встандартных приведе­ ниях типов.

Account a1("Jones",1122.5000);

// создать объект

int pin = (int)al;

// допустимые приведения

double bal = (double) a1;

const char *c = (const char*) a1;

 

Операция static_cast также действительна в этом контексте. Она выполняет

то же самое,что и стандартные операции приведения типов.

Account alC'Jones", 1122, 5000);

// создать объект

int pin = static_cast<int>(a1);

// ok

double bal = static_cast<double> (a1);

const char *c = static_cast<const char*>(a1);

He ошибитесь:операции static_cast работают только потому,что класс Account поддерживает перегруженные операции преобразования int, double и const char*. В противном случае попытка применения операции static_cast кобъек­ там Account была бы так же напрасна, как и попытка стандартных приведений.

Основная разница между стандартными приведениями и static_cast состоит в том, как они осуществляют преобразование указателей. Стандартные приведе­

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

В листинге 18.9 представлен пример использования преобразований указате­ ля. Результаты выполнения программы показаны нарис. 18.6.

Глава 18 • Программирование с обработкой исключительных ситуаций

829

При запуске main()два указателя pd и pi установлены для обозначе­ ния целой переменной i. Затем эти указатели разыменовываются, чтобы указывать на значение i. Целый указатель pi правильно отыскивает

значение i, ауказатель pd двойной длины извлекает мусор.

Затем указатель двойной длины pdустанавливается для указания на объект a1 класса Account. Разыменовывая этот указатель,программа не

только извлекает значение элемента данных объекта balance,но изаме­ няет его на любое необходимое.

1=9.88131 е-323 1=20 balance = 5000 balance = 10000

Рис. 18.6.

Вывод для программы из листинга 18.9

Листинг 18.9. Примеры преобразования указателя

 

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

 

#inclucle <iostream>

 

 

 

using

namespace std;

 

 

 

class

Account {

 

// базовый класс иерархии

protected:

 

// защищенные данные

double balance;

 

int

pin;

 

// identification

number

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; }

 

 

} ;

 

 

 

 

 

int mainO

 

 

 

 

 

double *pd, d=20.0; int i = 20, *pi - &i;

 

 

pd = (double*)

pi;

" i=" «

*pi «endl;

 

 

cout « "i=" «

*pd «

 

 

Account a1 ("Jones", 1122,5000);

/ /

создать объекты

pd = (double*)(&a1);

*pd «

endl;

 

 

cout « "balance = " «

 

 

*pd =10000;

 

*pd «

endl;

/ /

изменить элемент данных

cout « "balance = " «

 

 

return 0;

 

 

 

 

 

Здесь поведение static_cast отличается от поведения стандартных приведе­ ний. Указатель двойной длины способен неверно представить значение перемен­ ной i, потому что целый адрес может использоваться как операнд для приведения (double*). С помощью операции static_cast это сделать невозможно.

pd = (double*) pi;

//ok

pd = static_cast<double*> (pi);

// синтаксическая ошибка

Соседние файлы в предмете Программирование на C++