Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

1010

#include <stdexcept>

(a)void operate() throw( logic_error );

(b)int mathErr( int ) throw( underflow_error, overflow_error );

(c)char manip( string ) throw( );

Упражнение 19.6

Объясните, как механизм обработки исключений в C++ поддерживает технику программирования захват ресурса это инициализация; освобождение ресурса это уничтожение”.

Упражнение 19.7

#include <stdexcept>

int main() { try {

// использование функций из стандартной библиотеки

}

catch( exception ) {

}

catch( runtime_error &re ) {

}

catch( overflow_error eobj ) {

}

Исправьте ошибку в списке catch-обработчиков для данного try-блока:

}

Упражнение 19.8

int main() {

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

Дана программа на C++:

}

Модифицируйте main() так, чтобы она перехватывала все исключения, возбуждаемые функциями стандартной библиотеки. Обработчики должны печатать сообщение об ошибке, ассоциированное с исключением, а затем вызывать функцию abort() (она определена в заголовочном файле <cstdlib>) для завершения main().

19.3. Разрешение перегрузки и наследование A

Наследование классов оказывает влияние на все аспекты разрешения перегрузки функций (см. раздел 9.2). Напомним, что эта процедура состоит из трех шагов:

1. Отбор функций-кандидатов.

С++ для начинающих

1011

2.Отбор устоявших функций.

3.Выбор наилучшей из устоявших функции.

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

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

19.3.1. Функции-кандидаты

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

вида

func( args );

object.memfunc( args );

или функции-члена с помощью операторов доступа точкаили стрелка”: pointer->memfunc( args );

В данном разделе мы изучим оба случая.

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

namespace NS {

class ZooAnimal { /* ... */ }; void display( const ZooAnimal& );

}

// базовый класс Bear объявлен в пространстве имен NS class Bear : public NS::ZooAnimal { };

int main() { Bear baloo;

display( baloo ); return 0;

определены базовые классы. Например:

}

С++ для начинающих

1012

Аргумент baloo имеет тип класса Bear. Кандидатами для вызова display() будут не только функции, объявления которых видимы в точке ее вызова, но также и те, что объявлены в пространствах имен, в которых объявлены класс Bear и его базовый класс ZooAnimal. Поэтому в множество кандидатов добавляется функция display(const ZooAnimal&), объявленная в пространстве имен NS.

Если аргумент имеет тип класса и в определении этого класса объявлены функции-друзья с тем же именем, что и вызванная функция, то эти друзья также будут кандидатами, даже если их объявления не видны в точке вызова (см. раздел 15.10). Если аргумент при наследовании имеет тип класса, у которого есть базовые, то в множество кандидатов добавляются одноименные функции-друзья каждого из них. Предположим, что в

namespace NS { class ZooAnimal {

friend void display( const ZooAnimal& );

};

}

// базовый класс Bear объявлен в пространстве имен NS class Bear : public NS::ZooAnimal { };

int main() { Bear baloo;

display( baloo ); return 0;

предыдущем примере display() объявлена как функция-друг ZooAnimal:

}

Аргумент baloo функции display() имеет тип Bear. В его базовом классе ZooAnimal функция display() объявлена другом, поэтому она является членом пространства имен NS, хотя явно в нем не объявлена. При обычном просмотре NS она не была бы найдена. Однако поскольку аргумент display() имеет тип Bear, то объявленная в ZooAnimal функция-друг добавляется в множество кандидатов.

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

функций, видимых в точке вызова;

функций, объявленных в тех пространствах имен, где определен тип класса или любой из его базовых;

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

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

С++ для начинающих

1013

class ZooAnimal { public:

Time feeding_time( string ); // ...

};

class Bear : public ZooAnimal { public:

//скрывает ZooAnimal::feeding_time( string ) Time feeding_time( int );

//...

};

Bear Winnie;

// ошибка: ZooAnimal::feeding_time( string ) скрыта

Winnie.feeding_time( "Winnie" );

Функция-член feeding_time(int), объявленная в классе Bear, скрывает feeding_time(string), объявленную в ZooAnimal, базовом для Bear. Поскольку функция-член вызывается через объект Winnie типа Bear, то при поиске кандидатов для этого вызова просматривается только область видимости класса Bear, и единственным кандидатом будет feeding_time(int). Так как других кандидатов нет, вызов считается ошибочным.

Чтобы исправить ситуацию и заставить компилятор считать одноименные функции- члены базового и производного классов перегруженными, разработчик производного класса может ввести функции-члены базового класса в область видимости производного

class Bear : public ZooAnimal { public:

// feeding_time( int ) перегружает экземпляр из класса ZooAnimal using ZooAnimal::feeding_time;

Time feeding_time( int );

//...

спомощью using-объявлений:

};

Теперь обе функции feeding_time() находятся в области видимости класса Bear и,

// правильно: вызывается ZooAnimal::feeding_time( string )

следовательно, войдут в множество кандидатов:

Winnie.feeding_time( "Winnie" );

Втакой ситуации вызывается функция-член feeding_time( string ).

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

С++ для начинающих

1014

class Endangered { public:

ostream& print( ostream& ); // ...

{;

class Bear : public( ZooAnimal ) { public:

void print( );

using ZooAnimal::feeding_time; Time feeding_time( int );

// ...

};

class Panda : public Bear, public Endangered { public:

// ...

};

int main()

{

Panda yin_yang;

//ошибка: неоднозначность: одна из

//Bear::print()

//Endangered::print( ostream& ) yin_yang.print( cout );

//правильно: вызывается Bear::feeding_time() yin_yang.feeding_time( 56 );

}

При поиске объявления функции-члена print() в области видимости класса Panda будут найдены как Bear::print(), так и Endangered::print(). Поскольку они не находятся в одном и том же базовом классе, то даже при разных списках параметров этих функций множество кандидатов оказывается пустым и вызов считается ошибочным. Для исправления ошибки в классе Panda следует определить собственную функцию print(). При поиске объявления функции-члена feeding_time() в области видимости Panda

будут найдены ZooAnimal::feeding_time() и Bear::feeding_time() они расположены в области видимости класса Bear. Так как эти объявления найдены в одном и том же базовом классе, множество кандидатов для данного вызова включает обе функции, а выбирается Bear::feeding_time().

19.3.2. Устоявшие функции и последовательности пользовательских преобразований

Наследование оказывает влияние и на второй шаг разрешения перегрузки функции: отбор устоявших из множества кандидатов. Устоявшей называется функция, для которой

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

В разделе 15.9 мы показали, как разработчик класса может предоставить пользовательские преобразования для объектов этого класса, которые неявно вызываются

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

С++ для начинающих

1015

explicit. При наследовании на втором шаге разрешения перегрузки рассматривается более широкое множество таких преобразований.

Конвертеры наследуются, как и любые другие функции-члены класса. Например, мы

class ZooAnimal { public:

//конвертер: ZooAnimal ==> const char* operator const char*();

//...

можем написать следующий конвертер для ZooAnimal:

};

Производный класс Bear наследует его от своего базового ZooAnimal. Если значение типа Bear используется в контексте, где ожидается const char*, то неявно вызывается

extern void display( const char* );

Bear yogi;

// правильно: yogi ==> const char*

конвертер для преобразования Bear в const char*: display( yogi );

Конструкторы с одним аргументом без ключевого слова explicit образуют другое множество неявных преобразований: из типа параметра в тип своего класса. Определим

class ZooAnimal { public:

//преобразование: int ==> ZooAnimal ZooAnimal( int );

//...

такой конструктор для ZooAnimal:

};

Его можно использовать для приведения значения типа int к типу ZooAnimal. Однако конструкторы не наследуются. Конструктор ZooAnimal нельзя применять для

const int cageNumber = 8788l

void mumble( const Bear & );

// ошибка: ZooAnimal( int ) не используется

преобразования объекта в случае, когда целевым является тип производного класса: mumble( cageNumber );