и функции-члены). Это значит, что в принципе трудно сказать, относится ли имя члена к типу или нет. По техническим причинам, связанным с особенностями языка программирования, компилятор должен знать это, поэтому мы ему должны каким-то образом передать эту информацию. Для этого используется ключевое слово typename. Рассмотрим пример.
template<class T> struct Vec {
typedef T value_type; // имя члена
static int count; // данное-член
// ...
};
template<class T> void my_fct(Vec<T>& v)
{
int x = Vec<T>::count; // имена членов по умолчанию
// считаются относящимися не к типу
v.count = 7; // более простой способ сослаться
// на член, не являющийся типом
typename Vec<T>::value_type xx = x; // здесь нужно слово
// "typename"
// ...
}
Более подробная информация о шаблонах приведена в главе 19.
A.14. Исключения
Исключения используются (посредством инструкции throw) для того, чтобы сообщить вызывающей функции об ошибке, которую невозможно обработать на месте. Например, спровоцируем исключение Bad_size в классе Vector.
struct Bad_size {
int sz;
Bad_size(int s):ss(s) { }
};
class Vector {
Vector(int s) { if (s<0 || maxsize<s) throw Bad_size(s); }
// ...
};
Как правило, мы генерируем тип, определенный специально для представления конкретной ошибки. Вызывающая функция может перехватить исключение.
void f(int x)
{
try {
Vector v(x); // может генерировать исключения
// ...
}
catch (Bad_size bs) {
cerr << "Вектор неправильного размера (" << bs.sz << ")n";
// ...
}
}
Для перехвата всех исключений можно использовать инструкцию catch (...).
try {
// ...
} catch (...) { // перехват всех исключений
// ...
}
Как правило, лучше (проще, легче, надежнее) применять технологию RAII (“Resource Acquisition Is Initialization” — “выделение ресурсов — это инициализация”), чем использовать множество явных инструкций try и catch (см. раздел 19.5).
Инструкция throw без аргументов (т.е. throw;) повторно генерирует текущее исключение. Рассмотрим пример.
try {
// ...
} catch (Some_exception& e) {
// локальная очистка
throw; // остальное сделает вызывающая функция
}
В качестве исключений можно использовать типы, определенные пользователем. В стандартной библиотеке определено несколько типов исключений, которые также можно использовать (раздел Б.2.1). Никогда не используйте в качестве исключений встроенные типы (это может сделать кто-то еще, и ваши исключения могут внести путаницу).
Когда генерируется исключение, система поддержки выполнения программ на языке С++ ищет вверх по стеку раздел catch, тип которого соответствует типу генерируемого объекта. Другими словами, она ищет инструкции try в функции, генерирующей исключение, затем в функции, вызвавшей функцию, генерирующую исключение, затем в функции, вызвавшей функцию, вызвавшей функцию, которая генерирует исключение, пока не найдет соответствие. Если соответствие найдено не будет, программа прекратит работу. В каждой функции, обнаруженной на этом пути, и в каждой области видимости, в которой проходит поиск, вызывается деструктор. Этот процесс называется раскруткой стека (stack unwinding).
Объект считается созданным в тот момент, когда заканчивает работу его конструктор. Он уничтожается либо в процессе раскрутки стека, либо при каком-либо ином выходе из своей области видимости. Это подразумевает, что частично созданные объекты (у которых некоторые члены или базовые объекты созданы, а некоторые — нет), массивы и переменные, находящиеся в области видимости, обрабатываются корректно. Подобъекты уничтожаются, если и только если они ранее были созданы. Не генерируйте исключение, передающееся из деструктора в вызывающий модуль. Иначе говоря, деструктор не должен давать сбой. Рассмотрим пример.
X::~X() { if (in_a_real_mess()) throw Mess(); } // никогда так
// не делайте!
Основная причина этого “драконовского” правила заключается в том, что если деструктор сгенерирует исключение (или сам не перехватит исключение) в процессе раскрутки стека, то мы не сможем узнать, какое исключение следует обработать. Целесообразно всеми силами избегать ситуаций, в которых выход из деструктора происходит с помощью генерирования исключения, поскольку не существует систематического способа создания правильного кода, в котором это может произойти. В частности, если это произойдет, не гарантируется правильная работа ни одной функции или класса из стандартной библиотеки.
A.15. Пространства имен
Пространство имен (namespace) объединяет связанные друг с другом объявления и предотвращает коллизию имен.
int a;
namespace Foo {
int a;
void f(int i)
{
a+= i; // это переменная a из пространства имен Foo
// (Foo::a)
}
}
void f(int);
int main()
{
a = 7; // это глобальная переменная a (::a)
f(2); // это глобальная функция f (::f)
Foo::f(3); // это функция f из пространства имен Foo
::f(4); // это глобальная функция f (::f)
}
Имена можно явно уточнять именами их пространств имен (например, Foo::f(3)) или оператором разрешения области видимости :: (например, ::f(2)), который относится к глобальному пространству имен.
Все имена в пространстве имен (например, в стандартном пространстве std) можно сделать доступными с помощью директивы using namespace std;
Будьте осторожны с директивой using. Удобство, которое она предоставляет, достигается за счет потенциальной коллизии имен. В частности, старайтесь избегать директив using в заголовочных файлах. Отдельное имя из пространства имен можно сделать доступным с помощью объявления пространства имен.
using Foo::g;
g(2); // это функция g из пространства имен Foo (Foo::g)
Более подробная информация о пространствах имен содержится в разделе 8.7.
A.16. Альтернативные имена
Для имени можно определить альтернативное имя (alias); иначе говоря, можно определить символическое имя, которое будет означать то же самое, что и имя, с которым оно связано (для большинства случаев употребления этого имени).
typedef int* Pint; // Pint — это указатель на int
namespace Long_library_name { /* ... */ }
namespace Lib = Long_library_name; // Lib — это Long_library_name
int x = 7;
int& r = x; // r — это x
Ссылки (см.