выражение
имя-типа
Конструкция имя-шаблонного-класса является именем-класса ($$R.9).
Класс, который порождается шаблоном типа для класса, называется
шаблонным классом и эквивалентен обычному классу, определенному со
специальным именем - именем-шаблонного-класса, см. $$R.14.5.
Если в конструкции имя-шаблонного-класса имя-шаблона-типа не определено,
то она обозначает неопределенный класс.
Имя шаблонного класса должно быть уникальным в программе и в своей
области видимости оно не может обозначать другой шаблон типа,
класс, функцию, объект, значение или тип.
Типы, указанные в списке-парам-шаблона-типа из
имени-шаблонного-класса, должны соответствовать типам, заданным
в списке-параметров-шаблона-типа из шаблона-типа. (Можно сказать,
что первые являются фактическими параметрами шаблона типа, а вторые
- формальными.)
Кроме типов в списке-парам-шаблона-типа могут быть:
выражения-константы, адреса объектов или функций, подлежащих внешнему
связыванию, статические члены классов. Для параметров, не
являющихся типами, требуется точное соответствие ($$R.13.2).
Приведем примеры использования классов шаблона типа vector:
vector<int> v1(20);
vector<complex> v2(30);
typedef vector<complex> cvec; // cvec становится синонимом
// vector<complex>
cvec v3(40); // v2 и v3 одного типа
v1[3] = 7;
v2[3] = v3.elem(4) = complex(7,8);
Здесь vector<int> и vector<complex> являются шаблонными классами,
и их определения берутся по умолчанию из шаблона типа vector.
Поскольку шаблонное-имя-класса является именем-класса, то оно может
использоваться там, где допустимо имя-класса, например:
class vector<Shape*>
vector<Window>* current_window;
class svector : public vector<Shape*> { /* ... */ };
Определение функции-члена шаблонного класса дано в $$R.14.6.
Две конструкции шаблонное-имя-класса обозначают один и тот же класс,
если в них совпадают имена шаблонов типа и значения указанных
параметров. Например, в следующих описаниях x и y одного типа,
который отличен от типа z:
template<class E, int size> class buffer;
buffer<char, 2*512> x;
buffer<char,1024> y;
buffer<char,512> z;
Ниже приведены описания, в которых одинаковый тип имеют x2 и x3.
Он отличается от типов x1 и x4:
template<class T, void(*err_fct)()>
class list { /* ... */ };
list<int,&error_handler1> x1;
list<int,&error_handler2> x2;
list<int,&error_handler2> x3;
list<char,&error_handler2> x4;
Шаблон типа для функции определяет как будет строиться функция. Например,
семейство функций sort можно описать следующим образом:
template<class T> void sort(vector<T>);
Шаблон типа для функции порождает неограниченное множество перегруженных
функций. Функция, порождаемая шаблоном типа для функций, называется
шаблонной функцией. Она эквивалентна функции, в описании которой указан
тип, соответствующий шаблону, см. $$R.14.5.
При вызове шаблонной функции параметры шаблона типа не задаются
явно, вместо этого применяется правило разрешения неопределенности
перегруженных функций. Рассмотрим пример:
vector<complex> cv(100);
vector<int> ci(200);
void f(vector<complex>& cv, vector<int>& ci)
{
sort(cv); // вызывается sort(vector<complex>)
sort(ci); // вызывается sort(vector<int>)
}
Шаблонная функция может быть перегружена как обычными, так и
шаблонными функциями с тем же именем. Для разрешения неопределенности
шаблонных и обычных функций с одним и тем же именем надо
последовательно пройти три шага:
[1] Попытаться найти точно сопоставимую вызову ($$R.13.2) функцию,
и если она найдена, вызвать ее.
[2] Попытаться найти шаблон типа для функций, по которому
можно создать точно сопоставимую с рассматриваемым вызовом
функцию. Если удалось найти, то вызвать функцию.
[3] Попытаться применить обычное правило разрешения неопределенности
перегруженных функций ($$R.13.2). Если с его помощью
функция найдена, вызвать ее.
Если не найдено сопоставимой функции, вызов является ошибочным.
Если уже на первом шаге найдено более одного кандидата,
сопоставимого с данной функцией, то вызов также считается неоднозначным
и ошибочным.
Успешное выполнение шага [2] приведет к созданию некоторой
шаблонной функции с параметрами ($$R.14.5), типы которых точно
сопоставятся с типами параметров, указанных в вызове. В этом случае
недопустимо расхождение даже за счет тривиальных преобразований ($$R.13.2).
Такие же действия применяются для сопоставления типов указателей
на функции ($$R.13.3).
Рассмотрим пример:
template<class T> T max(T a, T b) { return a>b?a:b; };
void f(int a, int b, char c, char d)
{
int m1 = max(a,b); // max(int a, int b)
char m2 = max(c,d); // max(char c, char b)
int m3 = max(a,c); // ошибка: нельзя создать max(int,char)
}
Добавив к этому примеру описание
int max(int,int);
можно разрешить неопределенность для третьего вызова, поскольку теперь
задана функция, которая после стандартного преобразования char в int,
может сопоставиться с вызовом max(a,c).
Определение шаблона типа для функции используется для создания
различных вариантов шаблона типа. Для вызова определенного варианта
достаточно лишь описания шаблона типа.
Каждый параметр-шаблона-типа, который приведен в
списке-параметров-шаблона-типа должен обязательно использоваться при
задании типов параметров в шаблоне типа для функции.
template<class T> T* create(); //ошибка
template<class T>
void f() { // ошибка
T a;
// ...
}
Все параметры-шаблона-типа, приведенные в шаблоне типа для функции,
должны быть параметрами-типа.
Для каждого имени шаблона типа в программе должно существовать только
одно определение. Описаний может быть несколько. Определение
используется для создания специальных шаблонных классов и шаблонных
функций, которые будут соответствовать шаблону типа.
Конструкция имя-шаблонного-класса вводит описание шаблонного класса.
Вызов шаблонной функции или взятие ее адреса вводит описание
шаблонной функции. Для вызова или взятия адреса шаблонной функции
в языке существует особое соглашение: имя шаблонной функции
используется точно так же как имя обычной функции. Описание функции
с таким же именем, как у шаблонной функции, и с сопоставимыми типами
параметров, вводит описание специальной шаблонной функции.
Если для выполнения некоторых операций требуется определение
специального шаблонного класса или специальной шаблонной функции,
и если такого определения в программе нет, то оно будет создано.
Определение обычной (нешаблонной) функции с типом, который
точно сопоставляется с типом из описания шаблонной функции,
считается определением специальной шаблонной функции. Рассмотрим
пример:
template<class T> void sort(vector<T>& v) { /* ... */ }
void sort(vector<char*>& v) { /* ... */ }
Здесь определение функции sort будет использоваться для той функции
из семейства sort, которая сопоставляется при вызове с типом
параметра vector<char*>. Для других типов vector будет создаваться
соответствующее им определение функции по шаблону типа.
Можно определить класс, который задает шаблонный класс, например:
template<class T> class stream { /* ... */ };
class stream<char> { /* ... */ };
Здесь описание класса будет использоваться в качестве определения
потока символов (stream<char>). Другие потоки будут управляться
с помощью шаблонных функций, создаваемых по шаблону типа для функций.
Пока не появится описание шаблона типа для класса, никакие операции,
которым требуется определенный класс, не могут быть произведены над
шаблонным классом. После этого специальный шаблонный класс
будет считаться определенным, причем сразу же перед первым глобальным
описанием, использующим его имя.
Функция-член шаблонного класса считается неявной шаблонной функцией,
а параметры шаблона типа для ее класса - ее шаблонными параметрами.
Приведем пример, в котором описаны три шаблона типа для функции:
template<class T> class vector {
T* v;
int sz;
public:
vector(int);
T& operator[](int);
T& elem(int i) { return v[i]; }
// ...
};
Функцию, выполняющую индексацию, можно определить следующим образом:
template<class T> T& vector<T>::operator[](int i)
{
if (i<0 || sz>=i) error("vector: range error");
return v[i];
}
Шаблонный параметр для vector<T>::operator[]() будет задаваться
тем типом vector, к которому применяется операция индексации.
vector<int> v1(20);
vector<complex> v2(30);
v1[3] = 7; // vector<int>::operator[]()
v2[3] = complex(7,8); // vector<complex>::operator[]()
Функция-друг для шаблона типа не является неявной шаблонной функцией,
например:
template<class T> class task {
// ...
friend void next_time();
friend task<T>* preempt(task<T>*);
friend task* prmt(task*); // ошибка
// ...
};
Здесь функция next_time() становится другом всех классов task, а
каждый класс task имеет в качестве друга функцию preempt() c
соответствующими типами параметров. Функцию preempt() можно
определить как шаблон типа.
template<class T>
task<T>* preempt(task<T>* t) { /* ... */ }
Описание функции prmt() является ошибочным, поскольку типа
task не существует, а есть только специальные шаблонные типы
task<int>, task<record>, и т.д.
Для каждого шаблонного класса или функции, создаваемых по шаблону
типа, образуется своя копия статических переменных или членов.
Рассмотрим пример:
template<class T> class X {
static T s;
// ...
};
X<int> aa;
X<char*> bb;
Здесь в классе X<int> есть статический член типа int, а в классе
X<char> есть статический член типа char*.
Аналогично, в приведенном ниже примере, функция f(int*) имеет
статический член s типа int, а функция f(char**) имеет статический
член типа char**:
template<class T> f(T* p)
{
static T s;
// ...
}
void g(int a, char* b)
{
f(&a);
f(&b);
}
При обработке особых ситуаций в ходе выполнения программы информация и
управление передаются из некоторой точки обработчику особых
ситуаций. Обработчик находится в цепочке выполненных вызовов
функций. Управление обработчику передается с помощью
выражения-запуска, которое может быть только в проверяемом-блоке
обработчика или в функции, вызванной из проверяемого-блока.
проверяемый-блок:
try составной-оператор список-обработчиков
список-обработчиков:
обработчик список-обработчиков opt
обработчик:
catch ( описание-особой-ситуации ) составной-оператор
описание-особой-ситуации:
список-спецификаций-типа описатель
список-спецификаций-типа абстрактный-описатель
список-спецификаций-типа
...
выражение-запуска:
throw выражение opt
Конструкция проверяемый-блок является оператором ($$R.6), а
выражение-запуска - унарным выражением типа void ($$R.5). Иногда
выражение-запуска называют "точкой запуска", а про функцию, в которой
встретилось выражение-запуска, говорят, что она "запускает особую
ситуацию. Часть программы, которой передается управление из точки
запуска называется обработчиком.
При запуске особой ситуации управление передается обработчику. Запуск
сопровождается передачей объект, тип которого определяет, какой
обработчик должен перехватить особую ситуацию. Так, выражение
throw "Help!";
может быть перехвачено некоторым обработчиком с типом char*:
try {
// ...
}
catch(const char* p) {
// здесь обрабатывается особая ситуация в символьных строках
}
а особая ситуация Overflow (переполнение):
class Overflow {
// ...
public:
Overflow(char,double,double);
};
void f(double x)
{
// ...
throw Overflow('+',x,3.45e107);
}
может быть перехвачена обработчиком
try {
// ...
f(1.2);
// ...
}
catch(Overflow& oo) {
// здесь обработка особой ситуации типа Overflow
}
При запуске особой ситуации управление передается ближайшему
обработчику соответствующего типа. "Ближайший" - это обработчик,
проверяемый-блок которого последним получил управление и оно еще
не было передано оттуда. Что такое "соответствующий" тип
определяется в $$R.15.4.
При выполнении выражения-запуска создается временный объект
статического типа, который служит операндом в команде throw,
Этот объект используется для инициализации переменной,
соответствующего типа, описанной в обработчике. Если не считать
ограничений на сопоставление типов (см. $$R.15.4) и использование
временной переменной, то операнд throw аналогичен параметру функции
при вызове ($$R.5.2.2) или операнду в операторе return.
Если можно было бы, не меняя смысла программы за счет отказа
от вызовов конструкторов и деструкторов для временного объекта
($$R.12.1), обойтись совсем без временного объекта, то особую
ситуацию можно было бы непосредственно инициализировать в обработчике
параметром выражения запуска.
Если в выражении-запуска операнд не задан, то происходит
перезапуск обработки особой ситуации. Такое выражение-запуска
может появится только в самом обработчике или в функции, которая
непосредственно или опосредованно вызывается из него.
Например, фрагмент программы, который выполняется при обработке особой
ситуации, если нельзя еще полностью провести эту обработку, может
выглядеть так:
try {
// ...
}
catch (...) { // перехват всех особых ситуаций
// (частичная) обработка особых ситуаций
throw; // передача остальных особых ситуаций другому обработчику
}
Когда управление передается из точки запуска особой ситуации
обработчику, то вызываются деструкторы для всех автоматических
объектов, построенных с момента входа в проверяемый-блок.
Если объект не был построен полностью, то деструкторы
вызываются только для полностью построенных вложенных в него объектов.
Кроме того, если особая ситуация запускается в конструкторе при
построении элемента автоматического массива, то уничтожаться будут
только уже построенные элементы этого массива.
Процесс вызова деструкторов для уничтожения автоматических
объектов, построенных в ходе выполнения программы от начала
проверяемого-блока до выражения-запуска, называется "раскручиванием
стека".
Обработчик типа T, const T, T& или const& сопоставим с
выражением-запуска, имеющим операнд типа E, если:
[1] T и E являются одним типом;
[2] T является доступным ($$R.4.6) базовым классом E в точке
запуска;
[3] T является типом указателя, а E является таким типом указателя,
который можно в точке запуска преобразовать в T с помощью
стандартных преобразований указателя ($$R.4.6).
Рассмотрим пример:
class Matherr { /* ... */ virtual vf(); };
class Overflow : public Matherr { /* ... */ };
class Underflow : public Matherr { /* ... */ };
class Zerodivide : public Matherr { /* ... */ };
void f()
{
try {
g();
}
catch (Overflow oo) {
// ...
}
catch (Matherr mm) {
// ...
}
}
Здесь обработчик Overflow будет перехватывать ситуации типа
Overflow, а обработчик Matherr будет перехватывать ситуации типа
Matherr и всех типов, являющихся общими производными от Matherr,
включая Underflow и Zerodivide.
Обработчики в проверяемом-блоке подбираются для данной особой
ситуации в порядке их описания. Считается ошибкой , если обработчик
для базового класса помещен перед обработчиком для производного класса,
поскольку при таком расположении управление никогда не попадет
к обработчику для производного класса.
Эллипсис ... в описании-особой-ситуации действует так же как, и
в описании параметров функции, он сопоставим с любой особой
ситуацией. Если задан эллипсис, то использующий его обработчик
должен идти последним в проверяемом-блоке.
Если в проверяемом-блоке не произошло сопоставления ни с одним
из обработчиков, поиск соответствующего обработчика продолжается
в динамически объемлющем проверяемом-блоке. Если во всей программе
не произошло сопоставления ни с одним обработчиком, вызывается
функция terminate() ($$R.15.7).
Особая ситуация считается обработанной после входа в тело
обработчика. В этот момент завершится "раскручивание стека".
Возникновение и перехватывание особой ситуации влияет на
взаимодействие функций. Список особых ситуаций, которые прямо или
косвенно может запустить данная функция, можно задать как часть ее
описания. Конструкция спецификация-особой-ситуации
предшествует описателю функции.
спецификация-особой-ситуации:
throw ( список-типов opt )
список-типов:
имя-типа
список-типов , имя-типа
Приведем пример:
void f() throw (X,Y)
{
// ...
}
Если функция попробует запустить неуказанную в списке ситуацию,
управление передается функции unexpected(), см. $$R.15.8.
Реализация языка не должна запрещать выражение только потому,
что при его вычислении возможен запуск особой ситуации, не указанной
в спецификации-особой ситуации описания функции. Обработка непредвиденных
особых ситуаций происходит в динамике.
Функция, в которой отсутствует спецификация-особой-ситуации,
может запустить любую особую ситуацию.
Функция с пустой спецификацией-особых-ситуаций (throw()) не должна
запускать никаких особых ситуаций.
Если функция может запустить особую ситуацию класса X, то она
может запустить особую ситуацию любого класса, являющегося общим
производным классом от X.
Конструкция спецификация-особой-ситуации не относится к типу
функции.
Механизм управления особыми ситуациями использует для реакции на ошибки
при самой обработке особых ситуаций функции:
terminate() и unexpected().
Иногда от предусмотренной обработки особых ситуаций приходится
переходить к более грубым приемам, например:
- когда механизм управления особыми ситуациями не смог найти
обработчик для запущенной особой ситуации;
- когда механизм управления особыми ситуациями столкнулся
с нарушенной структурой стека;
- когда деструктор, вызванный в процессе раскрутки стека при запуске
особой ситуации, сам пытается завершить выполнение программы,
запустив особую ситуацию.
В этих случаях вызывается функция
void terminate();
Она в свою очередь вызывает функцию, которая была указана как параметр
при последнем обращении к set_terminate():
typedef void(*PFV)();
PFV set_terminate(PFV);
Функция, которая была задана в предыдущем вызове set_terminate(),
будет возвращаемым значением текущего вызова. Это помогает
пользователю реализовать алгоритм восстановления стека. По умолчанию
функция terminate() вызывает abort().
Выбор с помощью terminate() такой функции, которая вместо
действительного завершения программы, пытается вернуться в
вызвавшую программу, является ошибкой.
Если функция, имеющая спецификацию-особой-ситуации, запускает
неуказанную особую ситуацию, то вызывается функция
void unexpected();
Она в свою очередь вызывает функцию, которая была задана как
параметр при последнем обращении к set_unexpected():
typedef void(*PFV)();
PFV set_unexpected(PFV);
Функция, которая была задана в предыдущем вызове set_unexpected(),
будет возвращаемым значением текущего вызова. Это помогает
пользователю реализовать алгоритм восстановления стека. По умолчанию
unexpected() вызывает terminate(). Поскольку по умолчанию
terminate() вызывает abort(), результатом будет непосредственное
и точное обнаружение ошибки.
Для формального параметра операции catch действуют такие же
правила доступа, как и для формального параметра функции, в которой
задана операция catch.
При запуске особой ситуации можно указывать такой объект, который
можно копировать и уничтожать в области видимости функции, где
задана операция throw.
Реализация языка С++ включает препроцессор с возможностями
макроподстановки, условной трансляции и включения указанных
файлов.
Для передачи заданий препроцессору служат строки, начинающиеся
с символа # (перед ним могут идти пробелы и символы горизонтальной
табуляции). Такие строки называются командами, и их синтаксис
определяется независимо от остального языка. Команды могут
находиться в любом месте программы, и их действие продолжается
(независимо от правил областей видимости С++) до конца данной
единицы трансляции ($$R.2).
Команду препроцессора, как и любую строку, можно продолжить на
следующей строке входного текста, поместив символ обратной
дробной черты непосредственно перед символом конца продолжаемой
строки. Препроцессор до того, как входная строка будет разбита на
лексемы, удаляет символы обратной дробной черты и конца строки.
Символ обратной дробной черты не должен быть последним символом
входного файла.
К лексемам препроцессора относятся: лексемы самого языка ($$R.2.1),
имя файла, которое используется в команде #include и вообще любой
символ, отличный от обобщенного пробела и несовпадающий ни с какой
из лексем препроцессора.
По определению существует несколько фаз препроцессорной обработки.
В конкретной реализации фазы могут сливаться, но результат все равно
должен быть таким, как будто были выполнены все фазы.
Перечислим их.
При необходимости символы, зависящие от системы символы, обозначающие
конец строки, заменяются на стандартный символ конца строки. Аналогичной
замене подлежат все зависящие от системы символы. Определенные
последовательности символов (триграфы) заменяются на эквивалентный
им отдельный символ ($$R.16.2).
Удаляются все такие пары символов: обратная дробная черта, следующий
за ней символ конца строки. В результате будут слиты строки входного
текста, из которых была удалена эта пара.
Входной текст разбивается на лексемы препроцессора и последовательность
обобщенных пробелов. Каждый комментарий заменяется на один пробел.
Входной текст не должен кончаться посреди лексемы или комментария.
Выполняются команды препроцессора, и производятся макроподстановки
($$R.16.3, $$R.16.4, $$R.16.5, $$R.16.6, $$R.16.7 и $$R.16.8).
В символьных константах и строках литералов комбинации специальных
символов заменяются на свои эквиваленты ($$R.2.5.2).
Сливаются соседние строки литералов.
Результат препроцессорной обработки подвергается синтаксическому
и семантическому анализу, транслируется, а затем связывается с
необходимыми библиотеками и другими программами.
Прежде чем начнется какая-либо иная препроцессорная обработка,
каждое вхождение триграфной последовательности заменяется на один
символ в соответствии с приведенной ниже таблицей.
??= # ??( [
??/ \ ??) [
??' ^ ??! |
Например, строка
??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)
преобразуется в
#define arraycheck(a,b) a[b] || b[a]
Команда вида
#define идентификатор строка-лексем
называется макроопределением. Она указывает препроцессору, что надо
произвести замену всех последующих вхождений идентификатора на заданную
последовательность лексем, называемую строкой замены. Обобщенные
пробелы, окружающие эту последовательность лексем, отбрасываются.
Например, при определении
#define SIDE 8
описание
char chessboard[side][side];
после макроподстановки примет вид
char chessboard[8][8];
Определенный таким способом идентификатор можно переопределить
с помощью другой команды #define, но при условии, что строки
замены в обоих определениях совпадают. Все символы обобщенного
пробела, разделяющие лексемы, считаются идентичными.
Команда вида
идентификатор ( идентификатор, ... ,идентификатор) строка-лексем
называется макроопределением с параметрами или "функциональным"
макроопределением. В нем недопустимы пробелы между первым
идентификатором и символом (. Определенный таким способом
идентификатор можно переопределить с помощью другого функционального
макроопределения, но при условии, что во втором определении
то же число и те же наименования параметров, что и в первом, а обе
строки замены совпадают. Все символы обобщенного пробела, разделяющие
лексемы, считаются идентичными.
Последующие вхождения идентификатора, определенного в
функциональном макроопределении, если за ним следуют символ
(, последовательность лексем, разделенных запятыми, и символ
), заменяются на строку лексем из макроопределения. Обобщенные
пробелы, окружающие строку замены, отбрасываются. Каждое вхождение
идентификатора, из списка параметров макроопределения, заменяется
на последовательность лексем, представляющую соответствующий
фактический параметр в макровызове. Фактическими параметрами являются
строки лексем, разделенные запятыми. Запятая, взятая в кавычки, или
находящаяся в символьной константе или во вложенных круглых скобках,
не разделяет параметров. Число фактических параметров макровызова должно
совпадать с числом параметров макроопределения.
После идентификации параметров для функционального макроопределения
происходит подстановка фактических параметров. После выполнения подстановок
в параметре (если они были) этот параметр в строке замены замещается
фактическим параметром из макровызова ($$R.16.3.3); исключения
составляют случаи, когда параметру предшествует лексема # ($$R.16.3.1),
или с ним соседствует лексема ## ($$R.16.3.2).
Приведем пример. Пусть есть макроопределения
#define index_mask 0XFF00
#define extract(word,mask) word & mask
Тогда макровызов
index = extract(packed_data,index_mask);
после подстановки примет вид
index = packed_data & 0XFF00;
Для обоих видов макроопределений строка замены проверяется на
наличие других макроопределений ($$R.16.3.3).
Если непосредственно перед параметром в строке замены идет лексема
#, то при подстановке параметр и операция # будут заменены
на строку литералов , содержащую имя соответствующего параметра
имя-типа
Конструкция имя-шаблонного-класса является именем-класса ($$R.9).
Класс, который порождается шаблоном типа для класса, называется
шаблонным классом и эквивалентен обычному классу, определенному со
специальным именем - именем-шаблонного-класса, см. $$R.14.5.
Если в конструкции имя-шаблонного-класса имя-шаблона-типа не определено,
то она обозначает неопределенный класс.
Имя шаблонного класса должно быть уникальным в программе и в своей
области видимости оно не может обозначать другой шаблон типа,
класс, функцию, объект, значение или тип.
Типы, указанные в списке-парам-шаблона-типа из
имени-шаблонного-класса, должны соответствовать типам, заданным
в списке-параметров-шаблона-типа из шаблона-типа. (Можно сказать,
что первые являются фактическими параметрами шаблона типа, а вторые
- формальными.)
Кроме типов в списке-парам-шаблона-типа могут быть:
выражения-константы, адреса объектов или функций, подлежащих внешнему
связыванию, статические члены классов. Для параметров, не
являющихся типами, требуется точное соответствие ($$R.13.2).
Приведем примеры использования классов шаблона типа vector:
vector<int> v1(20);
vector<complex> v2(30);
typedef vector<complex> cvec; // cvec становится синонимом
// vector<complex>
cvec v3(40); // v2 и v3 одного типа
v1[3] = 7;
v2[3] = v3.elem(4) = complex(7,8);
Здесь vector<int> и vector<complex> являются шаблонными классами,
и их определения берутся по умолчанию из шаблона типа vector.
Поскольку шаблонное-имя-класса является именем-класса, то оно может
использоваться там, где допустимо имя-класса, например:
class vector<Shape*>
vector<Window>* current_window;
class svector : public vector<Shape*> { /* ... */ };
Определение функции-члена шаблонного класса дано в $$R.14.6.
Две конструкции шаблонное-имя-класса обозначают один и тот же класс,
если в них совпадают имена шаблонов типа и значения указанных
параметров. Например, в следующих описаниях x и y одного типа,
который отличен от типа z:
template<class E, int size> class buffer;
buffer<char, 2*512> x;
buffer<char,1024> y;
buffer<char,512> z;
Ниже приведены описания, в которых одинаковый тип имеют x2 и x3.
Он отличается от типов x1 и x4:
template<class T, void(*err_fct)()>
class list { /* ... */ };
list<int,&error_handler1> x1;
list<int,&error_handler2> x2;
list<int,&error_handler2> x3;
list<char,&error_handler2> x4;
Шаблон типа для функции определяет как будет строиться функция. Например,
семейство функций sort можно описать следующим образом:
template<class T> void sort(vector<T>);
Шаблон типа для функции порождает неограниченное множество перегруженных
функций. Функция, порождаемая шаблоном типа для функций, называется
шаблонной функцией. Она эквивалентна функции, в описании которой указан
тип, соответствующий шаблону, см. $$R.14.5.
При вызове шаблонной функции параметры шаблона типа не задаются
явно, вместо этого применяется правило разрешения неопределенности
перегруженных функций. Рассмотрим пример:
vector<complex> cv(100);
vector<int> ci(200);
void f(vector<complex>& cv, vector<int>& ci)
{
sort(cv); // вызывается sort(vector<complex>)
sort(ci); // вызывается sort(vector<int>)
}
Шаблонная функция может быть перегружена как обычными, так и
шаблонными функциями с тем же именем. Для разрешения неопределенности
шаблонных и обычных функций с одним и тем же именем надо
последовательно пройти три шага:
[1] Попытаться найти точно сопоставимую вызову ($$R.13.2) функцию,
и если она найдена, вызвать ее.
[2] Попытаться найти шаблон типа для функций, по которому
можно создать точно сопоставимую с рассматриваемым вызовом
функцию. Если удалось найти, то вызвать функцию.
[3] Попытаться применить обычное правило разрешения неопределенности
перегруженных функций ($$R.13.2). Если с его помощью
функция найдена, вызвать ее.
Если не найдено сопоставимой функции, вызов является ошибочным.
Если уже на первом шаге найдено более одного кандидата,
сопоставимого с данной функцией, то вызов также считается неоднозначным
и ошибочным.
Успешное выполнение шага [2] приведет к созданию некоторой
шаблонной функции с параметрами ($$R.14.5), типы которых точно
сопоставятся с типами параметров, указанных в вызове. В этом случае
недопустимо расхождение даже за счет тривиальных преобразований ($$R.13.2).
Такие же действия применяются для сопоставления типов указателей
на функции ($$R.13.3).
Рассмотрим пример:
template<class T> T max(T a, T b) { return a>b?a:b; };
void f(int a, int b, char c, char d)
{
int m1 = max(a,b); // max(int a, int b)
char m2 = max(c,d); // max(char c, char b)
int m3 = max(a,c); // ошибка: нельзя создать max(int,char)
}
Добавив к этому примеру описание
int max(int,int);
можно разрешить неопределенность для третьего вызова, поскольку теперь
задана функция, которая после стандартного преобразования char в int,
может сопоставиться с вызовом max(a,c).
Определение шаблона типа для функции используется для создания
различных вариантов шаблона типа. Для вызова определенного варианта
достаточно лишь описания шаблона типа.
Каждый параметр-шаблона-типа, который приведен в
списке-параметров-шаблона-типа должен обязательно использоваться при
задании типов параметров в шаблоне типа для функции.
template<class T> T* create(); //ошибка
template<class T>
void f() { // ошибка
T a;
// ...
}
Все параметры-шаблона-типа, приведенные в шаблоне типа для функции,
должны быть параметрами-типа.
Для каждого имени шаблона типа в программе должно существовать только
одно определение. Описаний может быть несколько. Определение
используется для создания специальных шаблонных классов и шаблонных
функций, которые будут соответствовать шаблону типа.
Конструкция имя-шаблонного-класса вводит описание шаблонного класса.
Вызов шаблонной функции или взятие ее адреса вводит описание
шаблонной функции. Для вызова или взятия адреса шаблонной функции
в языке существует особое соглашение: имя шаблонной функции
используется точно так же как имя обычной функции. Описание функции
с таким же именем, как у шаблонной функции, и с сопоставимыми типами
параметров, вводит описание специальной шаблонной функции.
Если для выполнения некоторых операций требуется определение
специального шаблонного класса или специальной шаблонной функции,
и если такого определения в программе нет, то оно будет создано.
Определение обычной (нешаблонной) функции с типом, который
точно сопоставляется с типом из описания шаблонной функции,
считается определением специальной шаблонной функции. Рассмотрим
пример:
template<class T> void sort(vector<T>& v) { /* ... */ }
void sort(vector<char*>& v) { /* ... */ }
Здесь определение функции sort будет использоваться для той функции
из семейства sort, которая сопоставляется при вызове с типом
параметра vector<char*>. Для других типов vector будет создаваться
соответствующее им определение функции по шаблону типа.
Можно определить класс, который задает шаблонный класс, например:
template<class T> class stream { /* ... */ };
class stream<char> { /* ... */ };
Здесь описание класса будет использоваться в качестве определения
потока символов (stream<char>). Другие потоки будут управляться
с помощью шаблонных функций, создаваемых по шаблону типа для функций.
Пока не появится описание шаблона типа для класса, никакие операции,
которым требуется определенный класс, не могут быть произведены над
шаблонным классом. После этого специальный шаблонный класс
будет считаться определенным, причем сразу же перед первым глобальным
описанием, использующим его имя.
Функция-член шаблонного класса считается неявной шаблонной функцией,
а параметры шаблона типа для ее класса - ее шаблонными параметрами.
Приведем пример, в котором описаны три шаблона типа для функции:
template<class T> class vector {
T* v;
int sz;
public:
vector(int);
T& operator[](int);
T& elem(int i) { return v[i]; }
// ...
};
Функцию, выполняющую индексацию, можно определить следующим образом:
template<class T> T& vector<T>::operator[](int i)
{
if (i<0 || sz>=i) error("vector: range error");
return v[i];
}
Шаблонный параметр для vector<T>::operator[]() будет задаваться
тем типом vector, к которому применяется операция индексации.
vector<int> v1(20);
vector<complex> v2(30);
v1[3] = 7; // vector<int>::operator[]()
v2[3] = complex(7,8); // vector<complex>::operator[]()
Функция-друг для шаблона типа не является неявной шаблонной функцией,
например:
template<class T> class task {
// ...
friend void next_time();
friend task<T>* preempt(task<T>*);
friend task* prmt(task*); // ошибка
// ...
};
Здесь функция next_time() становится другом всех классов task, а
каждый класс task имеет в качестве друга функцию preempt() c
соответствующими типами параметров. Функцию preempt() можно
определить как шаблон типа.
template<class T>
task<T>* preempt(task<T>* t) { /* ... */ }
Описание функции prmt() является ошибочным, поскольку типа
task не существует, а есть только специальные шаблонные типы
task<int>, task<record>, и т.д.
Для каждого шаблонного класса или функции, создаваемых по шаблону
типа, образуется своя копия статических переменных или членов.
Рассмотрим пример:
template<class T> class X {
static T s;
// ...
};
X<int> aa;
X<char*> bb;
Здесь в классе X<int> есть статический член типа int, а в классе
X<char> есть статический член типа char*.
Аналогично, в приведенном ниже примере, функция f(int*) имеет
статический член s типа int, а функция f(char**) имеет статический
член типа char**:
template<class T> f(T* p)
{
static T s;
// ...
}
void g(int a, char* b)
{
f(&a);
f(&b);
}
При обработке особых ситуаций в ходе выполнения программы информация и
управление передаются из некоторой точки обработчику особых
ситуаций. Обработчик находится в цепочке выполненных вызовов
функций. Управление обработчику передается с помощью
выражения-запуска, которое может быть только в проверяемом-блоке
обработчика или в функции, вызванной из проверяемого-блока.
проверяемый-блок:
try составной-оператор список-обработчиков
список-обработчиков:
обработчик список-обработчиков opt
обработчик:
catch ( описание-особой-ситуации ) составной-оператор
описание-особой-ситуации:
список-спецификаций-типа описатель
список-спецификаций-типа абстрактный-описатель
список-спецификаций-типа
...
выражение-запуска:
throw выражение opt
Конструкция проверяемый-блок является оператором ($$R.6), а
выражение-запуска - унарным выражением типа void ($$R.5). Иногда
выражение-запуска называют "точкой запуска", а про функцию, в которой
встретилось выражение-запуска, говорят, что она "запускает особую
ситуацию. Часть программы, которой передается управление из точки
запуска называется обработчиком.
При запуске особой ситуации управление передается обработчику. Запуск
сопровождается передачей объект, тип которого определяет, какой
обработчик должен перехватить особую ситуацию. Так, выражение
throw "Help!";
может быть перехвачено некоторым обработчиком с типом char*:
try {
// ...
}
catch(const char* p) {
// здесь обрабатывается особая ситуация в символьных строках
}
а особая ситуация Overflow (переполнение):
class Overflow {
// ...
public:
Overflow(char,double,double);
};
void f(double x)
{
// ...
throw Overflow('+',x,3.45e107);
}
может быть перехвачена обработчиком
try {
// ...
f(1.2);
// ...
}
catch(Overflow& oo) {
// здесь обработка особой ситуации типа Overflow
}
При запуске особой ситуации управление передается ближайшему
обработчику соответствующего типа. "Ближайший" - это обработчик,
проверяемый-блок которого последним получил управление и оно еще
не было передано оттуда. Что такое "соответствующий" тип
определяется в $$R.15.4.
При выполнении выражения-запуска создается временный объект
статического типа, который служит операндом в команде throw,
Этот объект используется для инициализации переменной,
соответствующего типа, описанной в обработчике. Если не считать
ограничений на сопоставление типов (см. $$R.15.4) и использование
временной переменной, то операнд throw аналогичен параметру функции
при вызове ($$R.5.2.2) или операнду в операторе return.
Если можно было бы, не меняя смысла программы за счет отказа
от вызовов конструкторов и деструкторов для временного объекта
($$R.12.1), обойтись совсем без временного объекта, то особую
ситуацию можно было бы непосредственно инициализировать в обработчике
параметром выражения запуска.
Если в выражении-запуска операнд не задан, то происходит
перезапуск обработки особой ситуации. Такое выражение-запуска
может появится только в самом обработчике или в функции, которая
непосредственно или опосредованно вызывается из него.
Например, фрагмент программы, который выполняется при обработке особой
ситуации, если нельзя еще полностью провести эту обработку, может
выглядеть так:
try {
// ...
}
catch (...) { // перехват всех особых ситуаций
// (частичная) обработка особых ситуаций
throw; // передача остальных особых ситуаций другому обработчику
}
Когда управление передается из точки запуска особой ситуации
обработчику, то вызываются деструкторы для всех автоматических
объектов, построенных с момента входа в проверяемый-блок.
Если объект не был построен полностью, то деструкторы
вызываются только для полностью построенных вложенных в него объектов.
Кроме того, если особая ситуация запускается в конструкторе при
построении элемента автоматического массива, то уничтожаться будут
только уже построенные элементы этого массива.
Процесс вызова деструкторов для уничтожения автоматических
объектов, построенных в ходе выполнения программы от начала
проверяемого-блока до выражения-запуска, называется "раскручиванием
стека".
Обработчик типа T, const T, T& или const& сопоставим с
выражением-запуска, имеющим операнд типа E, если:
[1] T и E являются одним типом;
[2] T является доступным ($$R.4.6) базовым классом E в точке
запуска;
[3] T является типом указателя, а E является таким типом указателя,
который можно в точке запуска преобразовать в T с помощью
стандартных преобразований указателя ($$R.4.6).
Рассмотрим пример:
class Matherr { /* ... */ virtual vf(); };
class Overflow : public Matherr { /* ... */ };
class Underflow : public Matherr { /* ... */ };
class Zerodivide : public Matherr { /* ... */ };
void f()
{
try {
g();
}
catch (Overflow oo) {
// ...
}
catch (Matherr mm) {
// ...
}
}
Здесь обработчик Overflow будет перехватывать ситуации типа
Overflow, а обработчик Matherr будет перехватывать ситуации типа
Matherr и всех типов, являющихся общими производными от Matherr,
включая Underflow и Zerodivide.
Обработчики в проверяемом-блоке подбираются для данной особой
ситуации в порядке их описания. Считается ошибкой , если обработчик
для базового класса помещен перед обработчиком для производного класса,
поскольку при таком расположении управление никогда не попадет
к обработчику для производного класса.
Эллипсис ... в описании-особой-ситуации действует так же как, и
в описании параметров функции, он сопоставим с любой особой
ситуацией. Если задан эллипсис, то использующий его обработчик
должен идти последним в проверяемом-блоке.
Если в проверяемом-блоке не произошло сопоставления ни с одним
из обработчиков, поиск соответствующего обработчика продолжается
в динамически объемлющем проверяемом-блоке. Если во всей программе
не произошло сопоставления ни с одним обработчиком, вызывается
функция terminate() ($$R.15.7).
Особая ситуация считается обработанной после входа в тело
обработчика. В этот момент завершится "раскручивание стека".
Возникновение и перехватывание особой ситуации влияет на
взаимодействие функций. Список особых ситуаций, которые прямо или
косвенно может запустить данная функция, можно задать как часть ее
описания. Конструкция спецификация-особой-ситуации
предшествует описателю функции.
спецификация-особой-ситуации:
throw ( список-типов opt )
список-типов:
имя-типа
список-типов , имя-типа
Приведем пример:
void f() throw (X,Y)
{
// ...
}
Если функция попробует запустить неуказанную в списке ситуацию,
управление передается функции unexpected(), см. $$R.15.8.
Реализация языка не должна запрещать выражение только потому,
что при его вычислении возможен запуск особой ситуации, не указанной
в спецификации-особой ситуации описания функции. Обработка непредвиденных
особых ситуаций происходит в динамике.
Функция, в которой отсутствует спецификация-особой-ситуации,
может запустить любую особую ситуацию.
Функция с пустой спецификацией-особых-ситуаций (throw()) не должна
запускать никаких особых ситуаций.
Если функция может запустить особую ситуацию класса X, то она
может запустить особую ситуацию любого класса, являющегося общим
производным классом от X.
Конструкция спецификация-особой-ситуации не относится к типу
функции.
Механизм управления особыми ситуациями использует для реакции на ошибки
при самой обработке особых ситуаций функции:
terminate() и unexpected().
Иногда от предусмотренной обработки особых ситуаций приходится
переходить к более грубым приемам, например:
- когда механизм управления особыми ситуациями не смог найти
обработчик для запущенной особой ситуации;
- когда механизм управления особыми ситуациями столкнулся
с нарушенной структурой стека;
- когда деструктор, вызванный в процессе раскрутки стека при запуске
особой ситуации, сам пытается завершить выполнение программы,
запустив особую ситуацию.
В этих случаях вызывается функция
void terminate();
Она в свою очередь вызывает функцию, которая была указана как параметр
при последнем обращении к set_terminate():
typedef void(*PFV)();
PFV set_terminate(PFV);
Функция, которая была задана в предыдущем вызове set_terminate(),
будет возвращаемым значением текущего вызова. Это помогает
пользователю реализовать алгоритм восстановления стека. По умолчанию
функция terminate() вызывает abort().
Выбор с помощью terminate() такой функции, которая вместо
действительного завершения программы, пытается вернуться в
вызвавшую программу, является ошибкой.
Если функция, имеющая спецификацию-особой-ситуации, запускает
неуказанную особую ситуацию, то вызывается функция
void unexpected();
Она в свою очередь вызывает функцию, которая была задана как
параметр при последнем обращении к set_unexpected():
typedef void(*PFV)();
PFV set_unexpected(PFV);
Функция, которая была задана в предыдущем вызове set_unexpected(),
будет возвращаемым значением текущего вызова. Это помогает
пользователю реализовать алгоритм восстановления стека. По умолчанию
unexpected() вызывает terminate(). Поскольку по умолчанию
terminate() вызывает abort(), результатом будет непосредственное
и точное обнаружение ошибки.
Для формального параметра операции catch действуют такие же
правила доступа, как и для формального параметра функции, в которой
задана операция catch.
При запуске особой ситуации можно указывать такой объект, который
можно копировать и уничтожать в области видимости функции, где
задана операция throw.
Реализация языка С++ включает препроцессор с возможностями
макроподстановки, условной трансляции и включения указанных
файлов.
Для передачи заданий препроцессору служат строки, начинающиеся
с символа # (перед ним могут идти пробелы и символы горизонтальной
табуляции). Такие строки называются командами, и их синтаксис
определяется независимо от остального языка. Команды могут
находиться в любом месте программы, и их действие продолжается
(независимо от правил областей видимости С++) до конца данной
единицы трансляции ($$R.2).
Команду препроцессора, как и любую строку, можно продолжить на
следующей строке входного текста, поместив символ обратной
дробной черты непосредственно перед символом конца продолжаемой
строки. Препроцессор до того, как входная строка будет разбита на
лексемы, удаляет символы обратной дробной черты и конца строки.
Символ обратной дробной черты не должен быть последним символом
входного файла.
К лексемам препроцессора относятся: лексемы самого языка ($$R.2.1),
имя файла, которое используется в команде #include и вообще любой
символ, отличный от обобщенного пробела и несовпадающий ни с какой
из лексем препроцессора.
По определению существует несколько фаз препроцессорной обработки.
В конкретной реализации фазы могут сливаться, но результат все равно
должен быть таким, как будто были выполнены все фазы.
Перечислим их.
При необходимости символы, зависящие от системы символы, обозначающие
конец строки, заменяются на стандартный символ конца строки. Аналогичной
замене подлежат все зависящие от системы символы. Определенные
последовательности символов (триграфы) заменяются на эквивалентный
им отдельный символ ($$R.16.2).
Удаляются все такие пары символов: обратная дробная черта, следующий
за ней символ конца строки. В результате будут слиты строки входного
текста, из которых была удалена эта пара.
Входной текст разбивается на лексемы препроцессора и последовательность
обобщенных пробелов. Каждый комментарий заменяется на один пробел.
Входной текст не должен кончаться посреди лексемы или комментария.
Выполняются команды препроцессора, и производятся макроподстановки
($$R.16.3, $$R.16.4, $$R.16.5, $$R.16.6, $$R.16.7 и $$R.16.8).
В символьных константах и строках литералов комбинации специальных
символов заменяются на свои эквиваленты ($$R.2.5.2).
Сливаются соседние строки литералов.
Результат препроцессорной обработки подвергается синтаксическому
и семантическому анализу, транслируется, а затем связывается с
необходимыми библиотеками и другими программами.
Прежде чем начнется какая-либо иная препроцессорная обработка,
каждое вхождение триграфной последовательности заменяется на один
символ в соответствии с приведенной ниже таблицей.
??= # ??( [
??/ \ ??) [
??' ^ ??! |
Например, строка
??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)
преобразуется в
#define arraycheck(a,b) a[b] || b[a]
Команда вида
#define идентификатор строка-лексем
называется макроопределением. Она указывает препроцессору, что надо
произвести замену всех последующих вхождений идентификатора на заданную
последовательность лексем, называемую строкой замены. Обобщенные
пробелы, окружающие эту последовательность лексем, отбрасываются.
Например, при определении
#define SIDE 8
описание
char chessboard[side][side];
после макроподстановки примет вид
char chessboard[8][8];
Определенный таким способом идентификатор можно переопределить
с помощью другой команды #define, но при условии, что строки
замены в обоих определениях совпадают. Все символы обобщенного
пробела, разделяющие лексемы, считаются идентичными.
Команда вида
идентификатор ( идентификатор, ... ,идентификатор) строка-лексем
называется макроопределением с параметрами или "функциональным"
макроопределением. В нем недопустимы пробелы между первым
идентификатором и символом (. Определенный таким способом
идентификатор можно переопределить с помощью другого функционального
макроопределения, но при условии, что во втором определении
то же число и те же наименования параметров, что и в первом, а обе
строки замены совпадают. Все символы обобщенного пробела, разделяющие
лексемы, считаются идентичными.
Последующие вхождения идентификатора, определенного в
функциональном макроопределении, если за ним следуют символ
(, последовательность лексем, разделенных запятыми, и символ
), заменяются на строку лексем из макроопределения. Обобщенные
пробелы, окружающие строку замены, отбрасываются. Каждое вхождение
идентификатора, из списка параметров макроопределения, заменяется
на последовательность лексем, представляющую соответствующий
фактический параметр в макровызове. Фактическими параметрами являются
строки лексем, разделенные запятыми. Запятая, взятая в кавычки, или
находящаяся в символьной константе или во вложенных круглых скобках,
не разделяет параметров. Число фактических параметров макровызова должно
совпадать с числом параметров макроопределения.
После идентификации параметров для функционального макроопределения
происходит подстановка фактических параметров. После выполнения подстановок
в параметре (если они были) этот параметр в строке замены замещается
фактическим параметром из макровызова ($$R.16.3.3); исключения
составляют случаи, когда параметру предшествует лексема # ($$R.16.3.1),
или с ним соседствует лексема ## ($$R.16.3.2).
Приведем пример. Пусть есть макроопределения
#define index_mask 0XFF00
#define extract(word,mask) word & mask
Тогда макровызов
index = extract(packed_data,index_mask);
после подстановки примет вид
index = packed_data & 0XFF00;
Для обоих видов макроопределений строка замены проверяется на
наличие других макроопределений ($$R.16.3.3).
Если непосредственно перед параметром в строке замены идет лексема
#, то при подстановке параметр и операция # будут заменены
на строку литералов , содержащую имя соответствующего параметра