Подобно тому, как класс является схемой для создания своих представителей-объектов, шаблон класса в C++ является схемой для образования конкретных представителей-классов шаблона, или шаблонных классов. Шаблоны классов называют иногда параметризованными типами, поскольку действительный тип (класс) создается посредством спецификации конкретных параметров шаблона.
Можно определять также шаблоны функций, с которых мы и начнем.
В подавляющем большинстве случаев прикладному программисту нужно только сбросить флажок External в диалоге Project Options и больше не беспокоиться о том, как написанные им шаблоны классов будут обрабатываться. Однако, если вы хотите создать, например, динамическую библиотеку на основе шаблона, которая будет содержать код всех его функций-элементов, то для генерирования полного представителя шаблона вам придется воспользоваться директивой template.
Следующий пример включает в себя два исходных файла и один заголовочный, в котором определяется простой шаблон. Главный исходный модуль программы создает два шаблонных объекта для его аргументов int и float, но, поскольку модуль компилируется с директивой #pragma option -Jgx, то никакого кода для представителей шаблона в нем не создается. Вместо этого во втором исходном модуле (компилируемом с ключом -Jgx) явным образом генерируется полный представитель шаблона для аргумента float, а также неявно генерируется представитель для int, так как модуль ссылается на него.
Листинг 10.4. Директива порождения представителя шаблона
//////////////////////////////////////////////////
// Simptmpl.h: Простой шаблон класса.
//
template <class T> class Simple {
protected:
int size;
int current;
T *arr;
public:
Simple(int) ;
~Simple () ;
void Insert(T item)
{ if (current != size) arr[current++] = item; }
T SGet(int) ;
};
template <class T> inline Simple<T>::Simple(int n): size(n),
current (0), arr(new T[n]) {}
template <class T> Simple<T>::~Simple() { delete[] arr; }
template <class T> T SSimple<T>::Get(int idx) { return arr[idx]; }
void Somefunc(int);
/////////////////////////////////////////////////////////
// Instance.cpp: Порождение представителей шаблона. //
#pragma option -Jgx
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
#include "Simptmpl.h"
USEUNIT("Somefunc.cpp") ;
#pragma argsused
int main(int argc, char* argv[])

Это ключевое слово может применяться в двух случаях. Во-первых, им можно заменять ключевое слово class в списке параметров шаблона. Такое дополнение сделано в ANSI C++ потому, что ключевое слово class в списке параметров не вполне отражает положение дел;
параметром шаблона может быть любой тип, а не только класс, и стандартный синтаксис может вводить некоторых в заблуждение. Следующие две нотации эквивалентны:
template <class T> class SomeClass {...};
template <typename T> class SomeClass {...};
Во-вторых, typename необходимо, если шаблон определяет некоторые объекты еще не объявленных типов. Рассмотрите такой шаблон функции:
template <class T>
void Func(Т Sargi, typename T::Inner arg2)
{
typename T::Inner tiObj;
// Локальный объект
// типа Т::Inner.
// ...

Нам еще как-то не приходилось говорить, что объявление класса может содержать вложенные объявления других типов, в том числе классов. Например:
class One { public:
class Two {
// Элементы класса Two...
} ;
private:
Two objOfTwo;
// Другие элементы One... }
Сослаться на вложенный тип можно либо через существующий объект, либо с помощью операции разрешения области действия с префиксом имени класса. Очень часто так объявляют перечисления и константы, пример чему вы могли видеть в классе ios, объявляющем перечислимые типы и их константы вида ios::fixed и т. п.
Предполагается, что любые классы, для которых будет вызываться шаблонная функция, должны объявлять тип с именем Inner. Но заранее неизвестно, что это за тип, и в этом случае объекты или аргументы функции, принадлежащие к этому типу, следует объявлять с ключевым словом
typename.
Заключение
В этой главе вы увидели, насколько широкие и мощные возможности предоставляют программисту шаблоны функций и классов. Можно легко объявлять нужные вам классы, создавая представители уже имеющихся шаблонов; существует много больших библиотек, реализующих этот принцип. К их числу относятся, например, библиотека контейнерных классов Борланда и Стандартная библиотека шаблонов ANSI C++ (STL), о которой мы немного расскажем в следующей главе.
Сброшенному флажку External в диалоге Project Options соответствует ключ командной строки компилятора -Jgd, а установленному — -Jgx. Посредством директивы препроцессора #pragma option можно указать ту или иную опцию для конкретного файла. Не следует забывать о том, что компиляция и компоновка программы — два различных этапа ее создания. Компоновщик не знает, что вы задали в диалоге Project Options для компилятора. В любом случае компоновщик не будет включать в исполняемый код повторные определения функций шаблона.
Определение шаблона класса предполагает:
определение собственно шаблона, синтаксис которого показан выше;
определение функций-элементов шаблона;
определение статических элементов данных.
Определения функций-элементов, расположенные в теле шаблона, ничем не отличаются от определения встроенных функций-элементов обычного класса. Определения функций-элементов, располагаемые вне тела шаблона, имеют такой вид:
template <список параметров шаблона> возвращаемый_тип имя шаблона< параметры_шаблона>::имя_функции(список_параметров) {тело_функции }
Подобным же образом определяются статические элементы данных шаблона класса:
template <список параметров шаблона>
тип имя шаблона
<параметры шаблона>::имя статического элемента[ =значение];
Смысл всех синтаксических элементов определений будет ясен, если рассмотреть пример законченного шаблона класса:
Листинг 10.2. Шаблон класса DataBase
///////////////////////////////////////////////////////
// Deftmpl.h: Пример определения шаблона класса.
//
#ifndef _DEFTMPL_H
#define _DEFTMPL_H
template <class T, int numRec> class DataBase { protected:
const int num.;
bool err;
T *base, *cp;
public:
DataBase (): num(numRec)
{
cp = base = new T[numRec];
err = false;
}
~DataBase () ( delete [] base;
} bool Error () { return err; } T SRec(void) ;
T &Rec(unsigned recno);
};
// Возвращает ссылку на текущую запись
// и переходит к следующей.,
template <class T, int numRec>
Т &DataBase<T, numReO: :Rec (void)
{
if (cp - base == num) { err = true;
return *(cp - 1) ;
}
else
return *cp++;
// Позиционирует указатель и возвращает ссылку
// на текущую запись.
template <class T, int numRec>
T &DataBase<T, numRec>::Rec(unsigned recno)
{
if (recno < (unsigned)num) { err=false;
return *(cp = base+recno);
}
else {
err=true;
return*(cp=base+num-1);
}
}
#endif
//_DEFTMPL_H
Этот шаблон реализует примитивный “поток” или “базу данных”, являющуюся массивом записей, тип которых определяется аргументом шаблона. Класс содержит функции для обращения либо к записи с указанным индексом (Rec (unsigned)), либо к записи, на которую ссылается указатель “базы данных” (Re с (void)). В последнем случае указатель перемещается к следующей записи.
При выходе за пределы массива устанавливается флаг ошибки, и функции Get () возвращают ссылку на последнюю запись.
Параметрами шаблона являются формальный тип записи Т и константа — число записей в массиве.

Функции-элементы шаблона класса, определяемые вне тела шаблона, могут объявляться как встроенные с помощь, ключевого слова inline, подобно функциям-элементам обычных классов. Например, в приведенном выше определении шаблона можно было бы написать:
template <class T,int numRec>
inline T &DataBase<T, numRec>::Rec(void)
{
if (cp-base== num) {err= true/return*(cp-1) ;
} else
return *cp++;
}
Шаблоны функций можно перегружать точно так же, как обычные функции. Два шаблона могут иметь одно и то же имя, если их можно различить по списку параметров, например:
// Возвращает больший из двух аргументов.
template <class Т> Т Мах(Т а, Тb) {
return а > b? а : b;
// Возвращает наибольший элемент массива. template <class Т> Т Мах(Т аrr[], size_t size)
(
Т maxVal = arr[0] ;
for(int i=l; i<size; i++) if (arr[i] > maxVal) maxVal = arr[i];
return maxVal;
}
Этот раздел посвящен тому, каким образом C++Builder генерирует шаблонные классы и функции в программах, состоящих из нескольких модулей исходного кода. Несколько модулей проекта могут подключать один и тот же заголовочный файл с шаблоном, и создавать объекты одного и того же шаблонного класса (с одинаковым набором аргументов шаблона). В C++Builder имеются средства, позволяющие избежать дублирования кода в такой ситуации.
В этом параграфе мы расскажем о некоторых возможностях шаблонов, предусмотренных в стандартном C++, но не реализуемых компилятором C++Builder. Нам кажется, что о них необходимо рассказать, хотя бы для того, чтобы, читая другие книги по C++, вы не пытались осуществить в C++Builder методики, которые на нем осуществить невозможно.
В конце концов, C++Builder не является универсальным инструментом. Он ориентирован на визуальное программирование, а те моменты, о которых мы будем здесь говорить, второстепенны с этой, да и, пожалуй, с любой другой точки зрения.
Если у вас есть Borland C++ 5 или более поздняя версия, и вы хотя бы немного умеете с ним работать, то можете при желании разобрать с его помощью приведенные ниже примеры.

Специализация шаблона класса
Подобно шаблону функции, шаблон класса может быть специализирован для специфического набора его аргументов. Для этого нужно написать явные реализации тех или иных методов шаблона для конкретных типов. Вот, например, шаблон, который генерирует класс массива объектов, в том числе символьных строк, для которых отдельно реализуется функция добавления в массив и деструктор:
#include <iostream.h>
#include <string.h>
const int DefSize = 4;
template <class T> Glass MyArray { protected:
int size;
int current;
T *arr;
public:
MyArray (int n = DefSize) { size = n;
current = 0;
arr = new T[size];
}
~MyArray ();
void Insert(const T Sitem);
T &Get(int idx) { return arr[idx]; } };
// Общий шаблон Insert:
template <class T> void MyArray<T>::Insert(const T Sitem)
{
if (current == size) return;
arr[current++] = item;
}
// Специализированная Insert для параметра char*:
void MyArray<char*>::Insert(char* const Sitem)
{
if (current == size) return;
arr[current] = new char[strlen(item) + 1];
strcpy(arr[current++], item);
}
// Общий деструктор:
template <class T>
MyArray<T>::-MyArray () ( delete[] arr; }
// Специализированный деструктор:
MyArray<char*>::-MyArray() (


Синтаксис определения шаблона функции имеет вид:
template <список формальных типов>возвращаемый_тип имя_функции(список параметров) {
тело функции }
Список_формальных_типов состоит из спецификаций вида class формалъный_тип, разделенных запятыми. Формальный тип может обозначаться любым идентификатором, аналогично формальному параметру функции.
Список _параметров функции должен включать в себя параметры типов, перечисленных в списке формальных _типов, и еще, возможно, какие-то другие. Возвращаемый_тип также может быть одним из формальных типов. Например:
template <class T> void Func1 (Та, Т b) { ... }
template <class T> T Func2(Т a, int b) { ... }
template <class Tl, class T2> long Func3(Tl a, T2 b) { ... }
Как видите, определение шаблона функции отличается от обычной функции только наличием конструкции template <список_формальных_типов> в заголовке.

В качестве примера шаблонов функций можно привести определение функций min () и max () из заголовочного файла sdlib.h. Определение это сводится к следующему:
template <class T> inline const Т &min(const Т&t1, const T&t2)
if (t1 < t2)
return tl;
else
return t2;
}
template <class T>
inline const T &max(const T &tl, const T &t2)
{
if (t1 > t2) return t1;
else
return t2;
}
Эти функции можно вызывать с аргументами любого типа (класса), в котором определены операции “больше-меньше”.
Когда компилятор встречает вызов функции шаблона, он автоматически порождает представитель шаблона, подставляя вместо формального типа конкретный тип аргумента, с которым вызывается функция.
Шаблоны функций размещают чаще всего в заголовочных файлах, подобно определениям макросов и inline-функций.
Шаблон класса является обобщенным определением некоторого семейства классов, имеющих схожую структуру, но различных в смысле используемых типов или констант. Синтаксис шаблона класса следующий:
template <список параметров шаблона> class имя шаблона {тело_класса };
В списке_параметров_шаблона .могут присутствовать элементы двух видов:
спецификации формальных констант, состоящие из имени некоторого типа с последующим идентификатором;
спецификации формальных типов, состоящие из ключевого слова class, за которым следует идентификатор; они аналогичны параметрам шаблона функции.
Чтобы создать из шаблона представитель конкретного класса, нужно конструировать объект, указав для его типа имя шаблона с набором конкретных аргументов (типов и констант). Каждый формальный тип в списке параметров шаблона нужно заменить на имя действительного типа. Каждая формальная константа заменяется на константу указанного в шаблоне типа:
// Шаблон класса. template <ciass T, int О class TmplClass { ... };
// Создание представителей шаблонных классов.
TmplClass<long, 100> IClassObj;
TmplClass<float, 40> *fClassPtr;
fClassPtr = new TmplClass<float, 40>;
После того, как представитель шаблонного класса создан, с ним можно обращаться точно так же, как с любым объектом, принадлежащим к обыч-
ному классу. Ниже показан пример программы, использующей определение шаблона из листинга 10.2.
Листинг 10.3. Создание и использование представителя шаблонного класса
///////////////////////////////////////////////
// Usetmpl.cpp: Использование шаблона класса. //
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
#include "Deftmpl.h"
// Включить определение шаблона.
// Класс записей, для которого будет создан шаблонный класс. class Record {
char str[41] ;
public:
Record(void) { str[0] = 0; }
void Set(const char *s)
{ strncpy(str, s, 40);}
char *Get(void)
{ return str; } };
#pragma argsused
int main(int argc, char* argv[])
{
const int NumRec = 4;
DataBase<Record, NumRec> db; // Объявление объекта
// с 4-мя записями.
// Инициализация массива.
db.RecO .Set("First string.");
db.Rec().Set("Second string.");
db.RecO .Set("Third string.");
db.Rec().Set("Fourth string.");
cout.setf(ios::boolalpha);
// Чтение с попыткой выхода за пределы массива.
db.Rec(O); // Позиционирование на 0.
for (int i=0; i<=NumRec; i++) {
cout << db.RecO .Get() << " Error: ";
cout << db.Error() << endl;
} cout << endl;
// Чтение с прямым указанием индекса.


Несколько напоминает перегрузку шаблонов ситуация, когда определяется обычная функция, имя которой совпадает с именем шаблона и список параметров которой соответствует шаблону с некоторым специфическим набором фактических типов. Такую функцию называют специализированной функцией шаблона. Этот прием применяют, когда для некоторого типа или набора типов общий шаблон работать не будет.
Допустим, мы хотим, чтобы шаблон Мах()из последнего примера порождал функцию для двух аргументов-строк, которая возвращала бы большую из них (в смысле алфавитного порядка). Функция Мах (char*, char*), порожденная из первого шаблона, сравнивала бы адреса строк вместо их содержимого. Поэтому нужно определить отдельную функцию Мах (char*, char*):
char *Max(char *a, char *b) {
return strcmp(a, b) > 0? а : b;
}

Когда компилятор встречает вызов какой-то функции, для его разрешения он следует такому алгоритму:
Сначала ищется обычная функция с соответствующими параметрами.
Если таковой не найдено, компилятор ищет шаблон, из которого можно было бы генерировать функцию с точным соответствием параметров.
Если этого сделать невозможно, компилятор вновь рассматривает обычные функции на предмет возможных преобразований типа параметров.
Ниже приводится полный пример, иллюстрирующий различные аспекты перегрузки и специализации шаблонов.
Листинг 10.1. Перегрузка и специализация шаблона
////////////////////////////////////////////////////////
// Functemp.cpp: Шаблоны функций.
//
#include <string.h>
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
// Возвращает больший из двух аргументов.
template <class Т> Т Мах(Т а, Т b) {
return a > b? a : b;
}
// Возвращает наибольший элемент массива.
template <class Т> Т Мах(Т аrr[], size_t size) {
Т maxVal = arr[0] ;
for(unsigned i=l; i<size; i++)
if (arr[i] > maxVal)
maxVal = arr[i] ;
return maxVal;
}
// Возвращает большую из двух строк.
char *Max(char *a, char *b)
{
return strcmp(a, b) > 0? а : b;

В диалоге Project Options на странице C++ имеется флажок Templates: External (рис. 10.4). По умолчанию он сброшен, что означает оптимальное, или “интеллигентное”, порождение представителей шаблонов классов и функций.

Рис. 10.4 Страница C++ диалога Project Options
При сброшенном флажке External компилятор порождает глобальные представители шаблонов для всех модулей, где создаются объекты шаблонных классов (или вызываются шаблонные функции). Это возможно только в том случае, если компилятор при обработке модуля “видит” все определение шаблона, со всеми его функциями-элементами. Однако объектный код не обязательно генерируется для всех шаблонных методов (определенных как не-встроенные). По умолчанию код генерируется для методов:
действительно вызываемых в модуле;
виртуальных;
всех методов явным образом генерированного представителя шаблона класса.
Что означает последний пункт, будет рассказано чуть ниже.
После этого компоновщик ilink32.exe анализирует код объектных файлов и помещает в исполняемый файл только один экземпляр функции для каждой комбинации шаблон/аргументы.
Если же флажок External будет установлен, то компилятор вообще не будет генерировать никакого кода для не-встроенных функций класса, а будет рассматривать их вызовы как внешние ссылки. Такой вариант может иметь смысл, если, допустим, вы используете в своем проекте библиотеку, в заголовочных файлах которой определяются некоторые шаблоны и в которой уже имеется компилированный код для всех имеющих смысл представителей шаблонов.
Алгоритмы стандартной библиотеки выполняют разные распространенные действия на наборах данных, представленных стандартными контейнерами. Среди этих действий можно назвать сортировку, поиск, замену и т. д. Ниже мы вкратце опишем имеющиеся в библиотеке алгоритмы, не вдаваясь в подробности и не приводя развернутых примеров. Применение стандартных алгоритмов достаточно очевидно и, как правило, не вызывает никаких затруднений.
Чтобы можно было вызывать эти алгоритмы, нужно включить в программу заголовок algorithm:
#include <algorithm>
using namespace std;
(Не забывайте указывать пространство имен std, когда пользуетесь новой нотацией включаемых файлов!)
Некоторые алгоритмы вы уже видели (например, find ()), так что здесь мы показываем в основном те, с которыми вы еще не встречались.
До сравнительно недавнего времени в языке C++ не было других стандартных средств программирования, кроме старой библиотеки стандартных функций С, которая совершенно не использовала мощных нововведений, таких, как классы, шаблоны, inline-функции и исключения. Библиотека стандартных шаблонов (Standard Template Library), разработанная в HP Laboratories, явилась в свое время весьма удачным шагом в решении проблемы стандартной библиотеки ANSI C++, в которую она теперь и входит.
Это весьма обширное собрание структур данных и алгоритмов общего назначения, которое позволяет решать самые различные задачи обработки наборов данных.
Битовое множество представляет собой контейнер, в котором могут храниться битовые последовательности фиксированной длины. Можно сказать, что оно служит представлением подмножеств фиксированного множества (в математическом смысле), каждому элементу которого соответствует один бит в определенной позиции. Единичный бит означает, что элемент принадлежит подмножеству, нулевой — что он не входит в данное подмножество. Подобным образом организованы множества языка Pascal.
Биты в bitset плотно упакованы, так что информация хранится очень экономно. Однако минимальный физический размер bitset равен 4 байтам — размеру int.
Создание битовых множеств
При создании битового множества указывается его размер (как аргумент шаблона). Можно инициализировать bitset строкой, состоящей из нулей и единиц:
bitset<32> bset1;
bitset<8> bset2(string ( "01011011"));

Вторая форма конструктора объявлена как explicit, с аргументом типа string, поэтому приходится делать явное преобразование литеральной строки в стандартную.
Действия над bitset
У класса bitset нет итераторов, и обращение к его элементам осуществляется по индексу.

Порядок следования элементов в bitset с точки зрения индексации является обратным расположению нулей и единиц в инициализирующей строке. (Это соответствует семантике двоичных чисел — слева стоит самый старший бит, которому обычно приписывают наибольший номер.)
Функция-элемент test() позволяет проверить состояние бита с указанным индексом. Функция апу() возвращает true, если хотя бы один из битов множества установлен.
Функции set () и reset () служат соответственно для установки и сброса битов множества. При указании индекса в качестве аргумента функция устанавливает/сбрасывает соответствующий бит; при вызове функции без аргумента устанавливаются/сбрасываются все биты множества.
Функция flip () инвертирует состояние указанного бита или всех битов множества.
К битовым множествам можно применять обычные логические поразрядные операции и сдвиги (~, &, |, ^, <<, >>).
Некоторые алгоритмы стандартной библиотеки C++ требуют функций в качестве параметров. Простейший пример — алгоритм for each (), который вызывает переданную ему функцию для каждого элемента контейнера. В этом разделе мы рассмотрим вопросы, связанные с функциональными параметрами алгоритмов.
Функции и предикаты
Иногда нужно выполнить какое-то действие для каждого элемента контейнера. Упомянутый выше алгоритм for_each() позволяет сделать именно это. Функция, выполняющая необходимое действие для отдельного элемента, передается как третий аргумент алгоритма. Первые два задают диапазон. Вот пример:
void Square(int arg) { return arg * arg; }
int main() {
vector<int> iVect;
for_each(iVect.begin (), iVect.end(), Square);
}
Двухместные функции принимают два параметра. Часто они применяются к элементам различных контейнеров. Например, имеется два списка, и нужно что-то сделать с элементом первого списка в зависимости от значения соответствующего ему элемента во втором. Это делается с помощью алгоритма transform (), одна из форм которого имеет вид
template <class Inputlteratorl, class Inputlterator2,
class Outputlterator, class Binary0peration>_
Outputlterator transform(Inputlteratorl firsti,
Inputlteratorl lasti,
Inputlterator2 first2,
Outputlterator result,
BinaryOperation binary_func);
Первые два параметра задают диапазон первого контейнера. Параметр first2 указывает начало второго контейнера. Контейнер, куда будет записан результат, начинается с result. Последний параметр — указатель на двухместную функцию преобразования.
Особо можно выделить функции- предикаты. Предикат — это функция, возвращающая булево значение. Они используются с алгоритмами типа find_if () , который находит в указанном диапазоне значение, для которого предикат истинен:
template <class Inputlterator, class Predicate>
Inputlterator find if(Inputlterator first,
Inputlterator last,
Predicate pred);
Функциональные объекты
Функциональный объект — это представитель класса, в котором определена операция вызова (скобки). Существуют различные ситуации, когда желательно передавать алгоритмам не функции, а функциональные объекты. Иногда это позволяет применить готовый функциональный объект стандартной библиотеки вместо новой функции; иногда — улучшить производительность благодаря генерированию встроенного кода. Кроме того, операция вызова может иметь доступ к информации, которая хранится в объекте — функциональный объект, в отличие от функции, обладает “памятью”.
| Функциональный объект |
Операция |
|
Арифметические |
|
| plus | сложение х + у |
| minus | .вычитание х - у |
| multiplies | умножение х * у |
| divides | деление х / у |
| modulus | остаток х % у |
| negate | смена .знака -х |
|
Отношения |
|
| equal to | равенство == |
| not equal to | неравенство != |
| greater | больше > |
| less | меньше < |
| greater equal | больше или равно >= |
| less equal | меньше или равно <= |
|
Логические |
|
| logical and | логическое И && |
| logical or | логическое ИЛИ | | |
| logical not | логическое отрицание ! |
Класс string (точнее, basic_string) имеет богатый набор функций, выполняющих обработку строк. Мы расскажем только о некоторых.
Присваивание и присоединение
Функции assign () и append () делают в общем-то то же, что и операции присваивания и сложения, однако обладают большими возможностями благодаря дополнительным параметрам. Можно, например, скопировать из одной строки в другую определенное количество символов, начиная с некоторой позиции:
string si("Some String already exists."), s2;
s2.assign (si, 5, 6); // Скопирует только слово "String".
Функции assign () и append () имеют аналогичные перегруженные формы и различаются только тем, что первая полностью заменяет текущее содержимое строки новым текстом, а вторая дописывает тот же текст в конец строки. Приведем все имеющиеся формы этих функций, чтобы дать читателю представление о том, что там вообще есть (возвращаемый тип пропущен — все они возвращают basic_string&):
append (const basic strings s);
append (const. basic_string& s, size type pos, size_type npos);
append (const charT* s, size type n);
append (const charT* s);
append (size_type n, charT с );
append (Inputlterator first, Inputlterator last);
assign (const basic strings s);
assign (const basic_string& s,
size__type pos, size_type n);
assign (const charT* s, size type n);
assign (const charT* s);
assign (size_type n, charT с);
assign (Inputlterator first, Inputlterator last);
Вставка и удаление
Функции insert () и erase () производят соответственно вставку указанного текста в заданное место строки и удаление фрагмента строки. Третья функция, replace (), является их комбинацией: делается вставка, а затем часть старого содержимого строки удаляется.
Приведем всего один пример, простейший:
string s1 ("First string.");
string s2(" and second");
si.insert(s1.find(' '),s2);
В данном случае insert () вставляет содержимое второй строки в первую перед найденным в ней пробелом. Следующий оператор удалит только что вставленную строку:

Как и контейнеры, строки характеризуются своим размером и вместимостью. Вот сводка функций-элементов, позволяющих манипулировать различными характеристиками строк. Они аналогичны соответствующим функциям контейнеров:
| Функция | Возвращаемый тип | Описание | |||
| size () | size type | Возвращает текущий размер строки. | |||
| length() | size type | Длина строки (то же, что и size). | |||
| capasity() | size type | Возвращает вместимость строки. | |||
| max size() | size type | Возвращает максимально возможный размер. | |||
| resize(n) | void | Изменение размера (может урезать строку). | |||
| reserve(n) | void | Резервирование по крайней мере n символов. | |||
| empty () | bool | Возвращает true, если строка пуста. |
Функции resize () и reserve () могут выбрасывать исключение length_error, если запрашиваемый размер больше максимально возможного (он обычно определяется размером наибольшего свободного блока памяти).
Итераторы, как было замечено выше, являются центральным механизмом, обеспечивающим работу с данными контейнеров. Они являются аналогом указателей и делают возможным циклический перебор всех элементов контейнера. Существуют разные виды итераторов, поскольку различные алгоритмы по-разному обращаются к данным. Каждый класс контейнера может порождать итераторы, необходимые для работы адекватных ему алгоритмов.
Подобно указателю, итератор может ссылаться на единственный элемент данных; пара итераторов может задавать определенный диапазон контейнера; итератор может иметь т. н. запредельное значение, аналогичное NULL и означающее, что его нельзя разыменовывать.

Следует упомянуть, что при вызове различных алгоритмов для диапазона, заданного парой итераторов, второй из них соответствует не последнему значению итератора в диапазоне, а следующему за ним.
Основными операциями над итераторами- являются, как и в случае указателей, разыменование и инкремент. Если итератор i после конечного ряда приращений может стать равным итератору j, то говорят, что итератор j достижим из i. Если к итератору, достигшему верхней границы диапазона, применить операцию инкремента, он примет запредельное значение.
Сделав такие предварительные замечания, мы перейдем теперь к конкретному изучению итераторов библиотеки стандартных шаблонов.
Типы итераторов
Существует пять основных форм итераторов:
Входной итератор обеспечивает доступ к контейнеру только для чтения в поступательном направлении (т. е. к итератору применима операция инкремента).
Выходной итератор обеспечивает доступ только для записи, также в поступательном направлении.
Поступательный итератор предоставляет доступ для чтения-записи в поступательном направлении.
Двунаправленный итератор допускает чтение и запись как в поступательном, так и реверсивном направлениях (к нему применимы как инкремент, так и декремент).
Итератор произвольного доступа предоставляет прямой доступ к данным для чтения-записи.
Итераторы, стоящие в этом списке ниже, выводятся из тех, что находятся выше. Это едва ли не единственный пример классовой иерархии в 8TL.
| Форма итератора | Контейнеры |
| входной итератор | istream iterator |
| выходной итератор | ostream iterator |
| двунаправленный итератор | List set и multiset map и multimap |
| итератор произвольного доступа |
обычные указатели vector deque |



Карты являются ассоциативными контейнерами и очень похожи на множества за исключением того, что в картах с ключами можно связать объекты произвольного типа. Ключ, таким образом, может служить своего рода индексом, по которому можно получить доступ к ассоциированному объекту. Соответственно, на картах определена операция индексации.
Ключевые значения в картах должны быть уникальны, в мультикартах они могут повторяться. Данные хранятся сортированными по ключевым значениям.
В общем, работа с картами практически ничем не отличается от работы с множествами. Только объявление карты выглядит несколько по-другому, поскольку в нем нужно указать дополнительно тип ассоциированных объектов.
Создание карт
В объявлении карты (мультикарты) требуется указать три аргумента шаблона: тип ключа, тип ассоциированного значения и класс функционального объекта, которым будет определяться способ упорядочения ключей. Обычно для простоты типу карты присваивается новое имя:
typedef map<string, double, less<string> > map_type;
Ключи такого контейнера будут строками, а ассоциированные объекты — значениями типа double. Сортирована карта будет в соответствии с алфавитным порядком ключей.
Кроме того, иногда бывает удобно объявить имя для типа элемента карты (т. е. по сути структуры, состоящей из ключа и ассоциированного типа объекта; подобные типы определяются с помощью шаблона pair<Tl, Т2>):
typedef map type::value_type val_type;
Обычно создается пустая карта, а затем в нее вводятся элементы функцией insert () . Возможно также конструирование новой карты из части уже существующей. При этом конструктору передаются, как обычно, два итератора.
Действия над картами
Важнейшие действия, выполняемые с картами и мультикартами — это поиск и извлечение данных по заданному ключу. Вообще-то карты в этом смысле практически полностью аналогичны множествам и имеют те же функции-элементы. Ниже приводится программа, демонстрирующая вариант “записной книжки” на основе мультикарты, которая может хранить несколько телефонных номеров для одного и того же имени-ключа. Нечто подобное мы уже делали в главе 8, когда говорили о перегрузке операции индексации, но индекс должен быть уникальным, а мультикарта позволяет иметь повторяющиеся ключи.

В стандартной библиотеке имеется десять шаблонов классов, реализующих различные структуры данных. Их перечень уже приводился в таблице 10.1. Каждый контейнер имеет свой тип iterator, через представители которого вы получаете доступ к данным. Благодаря тому, что контейнеры — это шаблоны, они обладают чрезвычайной общностью. В них можно хранить объекты, указатели на них и даже другие контейнеры, создавая, таким образом, многоуровневые структуры данных.
Есть некоторые основные принципы работы контейнеров, о которых всегда следует помнить. Среди них можно сформулировать следующие:
В За запись объекта в контейнер отвечает конструктор копии объекта. При копировании одного контейнера в другой может быть важна перегруженная операция присваивания.
Контейнеры сами автоматически выделяют и освобождают память по мере надобности.
Когда программа уничтожает контейнер, она первым делом вызывает деструкторы для всех содержащихся в нем объектов. (Это не касается случая, когда в контейнере хранятся не объекты, а указатели на них.)
Такое страшное название алгоритма означает всего-навсего, что он выполняет сравнение содержимого двух контейнеров, аналогичное сравнению текстовых строк. Элементы контейнеров могут быть любого типа, лишь бы для них была объявлена операция “меньше” (или какая-либо функция, задающая отношение сравнения):
bool
lexicographical_compare (Inputlteratorl first1,
Inputlteratorl last1,
Inputlterator2 first2,
Inputlterator2 last2);
bool lexicographical compare(Inputlteratorl first1,
Inputlteratorl last1,
Inputlterator2 first2,
Inputlterator2 last2,
Compare comp);
Алгоритм возвращает true, если содержимое первого контейнера меньше, чем второго.
Множества — это наборы уникальных значений; мультимножества допускают повторяющиеся значения. В остальном они совершенно идентичны, так что в дальнейшем я буду говорить просто о “множествах”.
Множество представляет собой ассоциативный контейнер с быстрым доступам к значениям элементов. Значения элементов в множестве принято называть ключами. Программа может быстро определить, находится ли данный ключ в множестве.
Элементы множества всегда сортированы. Поэтому поиск нужного ключа очень прост и эффективен.
Что касается последовательных операций и прямого доступа, то тут множества далеки от совершенства. Набор функций-элементов у множеств невелик по сравнению с другими контейнерами.
Создание множеств
Объявляются множества несколько сложнее, чем рассмотренные до сих пор контейнеры, так как при этом необходимо указать функциональный объект, который будет использоваться при упорядочении элементов:
set<double, less<double> > dset;

Обязательно вставьте пробел между двумя правыми угловыми скобками, а то компилятор примет их за операцию сдвига и откажется транслировать программу.
Удобно переименовать представитель шаблона:
typedef set<double, less<double> > set_type;
set type dset;
Множество, как и другие контейнеры, можно создать из диапазона элементов другого контейнера:
double darr[6] = (1.0, 2.0, 2.5, 4.5, 3.5, 2.5};
set_type dset(darr, darr + 6) ;
В каком бы порядке ни следовали элементы в исходном контейнере, в множестве они окажутся сортированными.

Если в множество set вводятся повторяющиеся элементы, они игнорируются. В multiset ключ будет содержаться столько раз, сколько раз он вводился.
Действия над множествами
Как я сказал, функций у множеств сравнительно немного. Функции insert () и erase () имеют дополнительную форму с одним параметром, специфицирующим ключ, который нужно добавить или удалить из множества:
dset.insert (3.14);
dset.erase(3.5);
Функции lower bound () и upper bound () возвращают соответственно итератор элемента, который больше или равен, и элемента, который больше указанного ключевого значения. Пример использования этих функций показан в приведенной ниже программе.
Накопление, или аккумуляция — это перебор заданного диапазона контейнера с суммированием (иди перемножением, или какой-то иной комбинацией) элементов в некоторой итоговой переменной. По умолчанию выполняется суммирование:
#include <numeric> double sum = accumulate(v.begin (), v.end(), 0.0);

Третий параметр алгоритма — начальное значение аккумулятора. При суммировании это обычно ноль. В качестве четвертого параметра можно задать функциональный объект, определяющий аккумулирующую операцию. Вот, например, как вычисляется произведение всех элементов вектора:
#include <numeric>
#include <functional>
double product = accumulate(v.begin(), v.end(),
1.0, multiplies<double> ());
Очередь отличается от стека порядком извлечения элементов: если в стеке операция pop () удаляет самый последний из помещавшихся в него элементов, то в очереди там же операция удаляет наиболее “старый” элемент. Получить значение этого элемента можно, вызвав функцию front ().
Очередь может быть конструирована на основе либо списка, либо deque. Вот пример, аналогичный примеру со стеком из предыдущего параграфа:
#include <queue>
#include <list>
queue<int, list<int> > iQueue;
for (int i=0; i<10; i++) iQueue.push (i);
while (!iQueue.empty ()) {
cout << iQueue.front() << end1;
iQueue.pop ();
}
Контейнеры deque (формально это сокращение означает “двусторонняя очередь”) комбинируют в себе свойства списков и векторов. Они допускают прямой доступ к элементам, но эффективно работают при вставках и удалениях.
У deque имеется операция индексации и отсутствуют функции, связанные с сортировкой, так как эти контейнеры могут работать со стандартными сортировками. В остальном они похожи на списки.
Для стандартных строк перегружен ряд операций.
Операция присваивания позволяет присвоить стандартной строке другую строку, строку С (или литерал), отдельный символ. Все показанные ниже присваивания допустимы:
char с = ' С ';
char cs[20] = "С string.";
string sOld("Source string.");
string sNew;
sNew = sOld;
sNew = cs;
sNew = "Literal string.";
sNew = c;
Перегруженная операция сложения выполняет конкатенацию строк, причем возможна как конкатенация двух строк с присвоением результата третьей строке, так и присоединение строки в конец другой строки с помощью присваивания +=:
string si("First"), s2("Second");
string s3;
s3 = si + " " + s2;
si += s2;
Строки можно индексировать. При обычной нотации индексации проверки диапазона не делается. Однако можно применить функцию at (), также возвращающую ссылку на символ строки с указанным индексом. В этом случае при выходе за текущую длину строки выбрасывается исключение out_of_range:
string s("A short string.");
try {
cout<< s.at(30) << endl;
) catch(out_of_range e) {
cout << "Range error: "<< end! << e.what() << endl;
}
Этот фрагмент кода выводит:
Range error:
position beyond end of string in function:
basic_string::at(size_t)
index: 30 is greater than max index: 15
Наконец, для стандартных строк перегружен весь набор операций отношений: равенство, неравенство, “больше”, “меньше” и т. д. Операции < и > производят лексикографическое сравнение в соответствии с алфавитным порядком.
Алгоритм random_shuffle () производит случайную перестановку элементов контейнера:
void random_shuffle(RandomAccessIterator first,
RandomAccessIterator last);
В качестве третьего аргумента можно указать функциональный объект с целым параметром, задающим диапазон генерируемых им случайных чисел.
Алгоритм может быть полезен не только для задач вроде тасовки колоды карт, но и для подготовки, например, тестовых наборов данных для программ сортировки и т. п.
Алгоритм count () осуществляет подсчет числа элементов контейнера с указанным значением. Алгоритм count_if() выполняет подсчет элементов, для которых выполняется условие заданного предиката:
void count(Inputlterator first, Inputlterator last,
const T& value, Size& count) ;
void count if(Inputlterator first, Inputlterator last,
Predicate p, Size& count);
Результат подсчета возвращается в четвертом параметре.
С алгоритмом поиска вы уже встречались не раз:
Inputlterator
find(Inputlterator first, Inputlterator last,
const T& value);
Inputlterator
find(Inputlterator first, Inputlterator last,
Predicate pred) ;
Вторая форма возвращает итератор первого элемента, для которого истинен указанный предикат.
Алгоритмы замены replace () и replace_if() позволяют заменять существующие значения контейнера новыми:
void replace(Forwardlterator first, Forwardlterator last,
const T& value, const T& new_value) ;
void replace_if(Forwardlterator first, Forwardlterator last,
Predicate pred, const T& new_value) ;
Наконец, последний из рассматриваемых здесь контейнеров стандартной библиотеки — это приоритетная очередь. Она строится на основе вектора или deque. От обычной очереди она отличается тем, что вне зависимости от порядка размещения элементов первым будет извлекаться наиболее “критический” из них. Критичность, или приоритет, элемента определяется заданным функциональным объектом отношения (по умолчанию — “меньше”). Наиболее приоритетный (наибольший) элемент помещается на вершину очереди (его значение доступно посредством функции top ()) и удаляется первым (функция pop ()).
Создание и действия с приоритетной очередью
При конструировании очереди в общем случае указывается тип элементов, тип контейнера-основы и функциональный объект, определяющий приоритеты. Контейнером-основой для приоритетной очереди может быть вектор или deque.

Нужно сказать, что шаблоны стеков и очередей имеют аргументы по умолчанию. Тип контейнера и отношение (для приоритетной очереди) указывать, вообще говоря, не обязательно. Так, для стека и очереди тип контейнера по умолчанию — deque, для приоритетной очереди — vector, а операция отношения — “меньше”.
Вот маленький пример, моделирующий составление списка неотложных дел в порядке их важности:
////////////////////////////////////////////////////
// Priority.срр: Демонстрация приоритетной очереди.
//
#include <iostream>
#include <string>
#include <queue>
#include <deque>
#pragma hdrstop
using namespace std;
class ToDo {
int priority;
string doit;
public:
ToDo(int p = 0, string d = ""): priority(p), doit(d) {}
bool operator<(const ToDo &arg) const { return priority < arg.priority; }
friend ostream &operator<<(ostreams, const ToDo&);
};
ostream &operator<<(ostream &os, const ToDo &t) {
os << t.priority << " - " << t.doit;
return os;
}
int main() {
priority_queue<ToDo, deque<ToDo>, less<ToDo> > todo;
// Разместим некоторые неотложные дела... todo.push(ToDo(3, "Finish the program you started yesterday."));
todo.push(ToDo(7, "Write a letter to X."));
todo.push(ToDo(4, "Buy some food for dinner."));
todo.push(ToDo(1, "Call your publisher."));
// Распечатать список в порядке срочности. while (!todo.empty()) {
cout << todo.top() << endl;
todo-pop() ;
)
return 0;
)
Программа выводит:
7 - Write a letter to X.
4 - Buy some food for dinner.
3 - Finish the program you started yesterday.
1 - Call your publisher.
С сортировкой мы уже встречались, правда, в виде функции контейнера (при изучении списков — их нельзя сортировать по-другому), а не отдельного алгоритма. Сортировка по умолчанию производится в восходящем порядке (используется операция < для сравнения элементов):
void sort(RandomAccessIterator first, RandomAccessIterator last);
void sort(RandomAccessIterator first,
RandomAccessIterator last. Compare comp);
Если более конкретно рассмотреть состав библиотеки стандартных шаблонов (исключив пока из рассмотрения строки), то в ней можно выделить следующие компоненты:
Итераторы, которые в некоторых отношениях подобны указателям. Это фундаментальное понятие STL; итераторы обеспечивают доступ к элементам данных контейнеров.
Контейнеры представляют собой структуры или наборы данных, такие, как списки, векторы, очереди, реализованные как шаблоны классов.
Уже упомянутые алгоритмы, представляющие собой шаблоны функций, оперирующих на данных контейнеров. Например, алгоритм может сортировать объекты, являющиеся элементами вектора или списка. Функции эти не обладают специфическими “знаниями” о типах и структурах, на которых они действуют.

Последнее, кстати, означает, что STL, если рассматривать ее как целое, не является объектно-ориентированной, поскольку данные и методы (т. е. алгоритмы) между собой не связаны. Организовать на основе библиотеки адекватные задаче классы и является обязанностью программиста, если он хочет написать действительно объектно-ориентированную, иерархически организованную программу.
Вместе с STL в C++Builder предусмотрены различные дополнительные средства, например, класс комплексных чисел и средства обработки ошибок.
Конструировать строки можно многими способами; можно создать пустую строку (конструктор по умолчанию), можно инициализировать строку литералом, присваиванием литерала или копированием, можно зарезервировать указанное число символов, инициализировав в то же время строку литералом меньшей длины, можно создать строку из стандартного контейнера (например, вектора), содержащего символы, и т. д. Ниже приводится ряд примеров конструирования строк.
string sEmpty;
string sLiteral("A string from literal.");
string sAssign = "A string by assign.";
string sCopy(sLiteral);
string sPart(sCopy, 14, 7);
string sFill(32, '#') ;
Пояснений, вероятно, требуют только два последних конструктора. Предпоследний создает строку из уже существующей, выделяя ее подстроку длиной 7 символов, начиная с индекса 14. Последний конструктор создает строку длиной 32 символа, заполняя ее символами ' # '.
Списки — двусвязные линейные структуры данных, т. е. каждый элемент имеет два указателя для ссылки на два других элемента. Списки занимают ровно столько памяти, сколько необходимо для хранения наличных элементов. Вставка и удаление из списка очень эффективны. Слабым местом списка является доступ к элементам. Прямой доступ, как в векторах, невозможен. Для поиска нужного элемента приходится двигаться по списку, начиная с самого начала.
Создание списков
Существуют различные способы конструирования списков.
#include <list>
list<int>ilist;
list<double>dlist(20, 1.0);
list<MyType>mtlist(10) ;
Эти объявления имеют тот же смысл, что и соответствующие объявления для векторов. Так же как и вектор, список можно конструировать, инициализировав содержимым другого контейнера:
int iarr[5] = {1, 2, 3, 4, 5};
…
list<int> linti(iarr, iarr + 5);
В списке можно хранить любой тип данных, при условии, что он поддерживает те функции-элементы (конструкторы и проч.), о которых говорилось выше при обсуждении векторов.
Действия над списками
Поскольку список занимает ровно столько памяти, сколько необходимо, для него не имеет смысла понятие вместимости. Поэтому у списков нет функций capacity () и reserve (). Невозможно и обращение к элементам по индексу.
В остальном над списками можно производить все те операции, что описывались в предыдущем параграфе о векторах. Но следует упомянуть о некоторых дополнительных возможностях списков.
Помимо известных вам уже методов push back() и pop back (), имеются функции push_front () и pop_front () для добавления или удаления элемента в начале списка.
Функция remove () удаляет из списка все элементы с указанным значением.
Функция unique () удаляет все повторяющиеся элементы (стоящие;
подряд, поэтому функцию имеет смысл применять только на сортированных списках), оставляя только первое вхождение элемента с данным значением.
Функция reverse () обращает порядок элементов в списке.
Функция sort () (без аргумента) производит сортировку списка в соответствии с операцией “меньше”, т. е. в восходящем порядке. Можно задать в качестве аргумента функциональный объект, реализующий отношение, в соответствии с которым нужно сортировать список:


Под стандартными строками понимают объекты, принадлежащие шаблону basic_string, чаще всего его классам-представителям string или wstring. В повседневном .программировании применяется почти исключительно класс string.
Стандартные строки имеют ряд преимуществ перед строками “в стиле С”, т. е. строками с ограничивающим нулем, хранящимися в массивах типа char. Строки стандартной библиотеки шаблонов можно считать контейнерами, однако они реализованы совершенно отдельно от остальных контейнеров, рассматривавшихся в предыдущем разделе.
Чтобы можно было работать со стандартными строками, необходимо включить в программу заголовок string. При этом, кстати, автоматически подключается заголовок С string, h, так что вы можете при этом пользоваться стандартными функциями С для строк, ограниченных нулем.
Стек — очень простая структура данных. В STL можно организовать три разновидности стеков: на основе вектора, на основе списка и на основе deque. Функционально они не отличаются друг от друга.
Создание и действия со стеками
При конструировании стека нужно указать не только тип хранящихся в нем объектов, но и тип контейнера, на основе которого стек будет реализован:
#include <stack>
#include <vector>
stack<int, vector<int> > iStack;
Функция push () помещает указанное значение на вершину стека;
функция pop () удаляет из стека верхнее значение. Получить значение с вершины стека можно функцией top ():
for (int i=0; i<10; i++) iStack.push(i) ;
while (!iStack.empty()) {
cout<< iStack.topO << endl;
iStack.pop();
}
Удаление элементов контейнера с указанным значением выполняется алгоритмами remove () и remove_if:
Forwardlterator
remove(Forwardlterator first, Forwardlterator last,
const T& value) ;
Forwardlterator
remove if(Forwardlterator first, Forwardlterator last,
Predicate pred) ;
Необходимо заметить, что эти алгоритмы не уменьшают числа элементов в контейнере. Они только сдвигают элементы, которые должны остаться в новом наборе, к его началу, и возвращают итератор конца нового набора элементов. Чтобы действительно удалить ненужные элементы, нужно применить метод контейнера erase ():
array.erase(remove(array.first(), array.end(), value),
array.end());
Алгоритм unique () удаляет из контейнера все элементы с повторяющимися значениями, следующие друг за другом, оставляя только первый из них:
Forwardlterator remove(Forwardlterator first, Forwardlterator last);
Алгоритм возвращает итератор конца нового набора элементов.
Вектор — это своего рода массив с “интеллектом”, помнящий свой размер и поддерживающий свою целостность. Они, среди прочих контейнеров, отличаются эффективностью доступа к элементам. К недостаткам следует отнести сложность вставки элементов в середину вектора, поскольку при этом приходится перемещать часть его содержимого.
Создание векторов
Объявления векторов могут выглядеть примерно так:
#include <vector>
vector<int> vint;
vector<double> vdouble (100);
vector<string> vstr(10);
vector<MyClass> myvect(20);
Короче говоря, вы можете создать либо пустой вектор, либо вектор указанного размера. Кроме размера, можно указать также начальное значение, которое будет присвоено всем элементам:
vector<int> vint1(24, -1);
Можно объявить вектор из объектов своего собственного класса. Следует при этом иметь в виду, что любой структурный тип должен иметь:
конструктор по умолчанию
конструктор копии
деструктор
операцию взятия адреса
операцию присваивания.

На самом деле для простых классов, не содержащих указателей на какие-либо динамические объекты, годятся те функции-элементы, что компилятор генерирует сам, за исключением разве что конструктора по умолчанию. (Он генерируется автоматически, только если вы не определили вообще никаких конструкторов. Создаваемые таким конструктором объекты будут содержать “мусор”.) Советую вам обязательно объявлять, хотя бы пустой, конструктор по умолчанию для классов, которые будут размещаться в контейнерах.
Естественно, потребуются еще какие-то механизмы присваивания и извлечения значений элементов данных создаваемого объекта и т. п. Но это вопрос внешний по отношению к функционированию контейнера.
Если вы хотите выполнять поиск или сортировку, потребуются операции равенства (==) и “меньше” (<).
Скелет класса, пригодного для помещения в вектор, может иметь такой вид:
class Coord {
int x, у;
public:
Coord() : x(0), у(0) {}
void set(int _x, int y) { x = x; у = _y; }
void get(int &_x, int &_y) { _x = x, _y = y; ) };
Состав библиотеки стандартных шаблонов можно разбить на две категории: алгоритмы и структуры данных. Из последних можно выделить стандартные строки, поскольку они реализованы совершенно независимо от остальных элементов библиотеки. Строки мы будем обсуждать отдельно и подробнее, чем все остальное.
Наиболее прост вывод строк с помощью операции <<:
string s("This is a string!");
cout << s << end1;
По форме это ничем не отличается от вывода строки С. Ввод строки вроде бы тоже выглядит очень просто:
string s;
cin >> s;
Однако такой оператор считывает в строку только одно “слово” до первого пробельного символа. Чтобы прочитать всю введенную строку вплоть до ограничителя, нужно воспользоваться функцией getline ():
string s;
getline(cin, s, '\n');
Ограничитель ' \n ' можно было бы и не указывать, так как он принимается по умолчанию.
Стандартная библиотека C++ вводит новый стиль спецификации заголовочных файлов. Расширение .h опускается. Например, для подключения библиотеки алгоритмов нужно написать
#include <algorithm>
Компилятор автоматически укорачивает имя до восьми символов, добавляет .h и читает файл algorith.h из каталога $(BCB)\Include. На уровне исходного кода программы C++ получаются более мобильными, не привязанными к конкретной системе именования файлов.
Следующая таблица перечисляет стандартные заголовки STL с краткими описаниями контейнерных классов, которые они содержат.
Таблица 10.1. Контейнерные классы STL
|
Директива #include |
Класс контейнера |
||
| <bitset> | bitset — множества как битовые наборы. | ||
| <deque> | deque — двусвязные очереди; имя является сокращением от “double-end queue”. | ||
| <iist> | list — списки. | ||
| <map> | map, multimap — карты; это структуры, подобные массиву, но в которых роль “индекса” могут играть не только целые числа, но любые упорядоченные типы. | ||
| <queue> | queue, priority queue — очереди, т. е. структуры, организованные по принципу “первым вошел, первым вышел”. | ||
| <set> | set, multiset — множества. | ||
| <stack> | stack — стеки, организованные по принципу “последним вошел, первым вышел”. | ||
| <vector> | vector, vector<bool> — векторы, во многом подобные обычным массивам. |
За пробным блоком следует один или несколько обработчиков исключения, начинающихся ключевым словом catch. За ним следует объявление исключения в круглых скобках, аналогичное формальному параметру функции:
try {
}
catch(int. i) { // Перехватывает исключения типа int.
} catch(char* str) { // Перехватывает char*.
} catch (...) { // Перехватывает все остальное.
Если тип выброшенного в пробном блоке исключения совпадает или совместим (об этом позже) с типом в объявлении некоторого обработчика, то данный обработчик перехватывает исключение. Если нет, то поиск подходящего обработчика продолжается далее. Обработчик, в заголовке которого вместо объявления исключения стоит многоточие (...), перехватывает исключения любого типа; такой обработчик должен быть последним в ряду тех, что следуют за данным блоком try.
Если пробный блок не генерировал никакого исключения, управление, по выходе из него, передается первому оператору, следующему за последним из обработчиков исключений.
Ключевое слово try начинает пробный блок операторов, показывая, что данный блок может генерировать исключение. Тело блока заключается в фигурные скобки. Оно может содержать вызовы функций, тело которых при этом тоже будет рассматриваться как принадлежащее пробному блоку. Другими словами, весь код, могущий прямо или косвенно исполняться при входе в блок, принадлежит пробному блоку:
try {
cout << "Входим в пробный блок..."<< end.1;
DangerousFunc(); // Вызов процедуры, способной
// генерировать исключение.
}
// Конец try-блока.
Блоки try могут быть вложенными.
Имеются три глобальные переменные, в которых хранится информация о текущем исключении (они объявлены в заголовке except, h):
_throwExceptionName содержит указатель на строку с именем типа исключения;
_throwFileNane содержит указатель на строку с именем файла, где произошло исключение;
_throwLineNumber — целое без знака, представляющее номер строки файла, где было выброшено исключение.
Чтобы эта информация стала доступной, на странице C++ диалога Project Options нужно установить флажок Location information в группе Exception handling (по умолчанию выключен). Он соответствует ключу командной строки -хр.
Вот пример:
////////////////////////////////////////////////////
// Loclnfo.cpp: Информация о точке выброса исключения.
//
#include <iostream.h>
#include <stdexcept>
#pragma hdrstop
#include <condefs.h>
void f() {
throw invalid_argument("Exception from f(): ");
}
int main() {
try { f () ;
}
catch(const exception &e) { cout << e.what()
<< _throwExceptionName << end1
<< " in file " << _throwFileName << end1
<< " line " << _throwLineNumber << end1;
}
return 0; }
Программа печатает:
Exception from f(): invalid argument
in file C:\Projects\Chl2\LocInfo\LocInfo.cpp line 8
Как уже упоминалось, выбрасывать можно не только выражения простых типов, но и объекты классов, определенных пользователем. Кроме того, нужно рассмотреть еще ряд вопросов, связанных с работой конструкторов и деструкторов во взаимодействии с механизмом исключений.
Когда выбрасывается исключение, управление передается некоторому обработчику; он должен принадлежать функции, еще находящейся на стеке вызовов программы (см. в главе 5 об окне стека вызовов). Но эта функция не обязательно будет текущей функцией (выбросившей исключение). Например, пробный блок и обработчики находятся в одной функции, а исключение выбрасывается некоторой функцией, вызываемой из пробного ; блока; между ними может тянуться целая цепочка вложенных вызовов.
В результате некоторой последовательности вызовов на стеке (физическом) будут находиться адреса возврата, передаваемые аргументы и локальные переменные вызванных к данному моменту функций, а также вспомогательная информация (формирующая кадры стека), которая используется кодом входа и выхода из функции.
При нормальном возврате из функции она удаляет со стека локальные переменные, вызывая соответствующий деструктор, если переменная — представитель класса. Затем она восстанавливает кадр стека вызывающей функции и исполняет инструкцию возврата. После этого вызывающая функция удаляет со стека передававшиеся аргументы, также вызывая при необходимости деструкторы.
Приведем небольшую иллюстрацию. Ниже показана программа, состоящая из main () и двух функций FuncA () и FuncB () . Главная функция создает объект класса S и передает его FuncA (), которая его модифицирует и передает в FuncB (). Затем управление возвращается к main () .
Листинг 12.2. Работа стека при вызовах функций
///////////////////////////////////////////////////
// Stack.срp: Работа стека.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
struct S // Простой класс. {
int s;
S(int ss): s(ss) // Конструктор (преобразования из int).
{
cout << "Constructor for "<< s << endl;
} S (const S& src) // Конструктор копии.
{
s = src.s;
cout << "Copy constructor for " << s << endl;
}
~S() // Деструктор.
{
cout << "Destructor of " << s << endl;
Следующие два параграфа посвящены процессам, происходящим при генерировании исключения в конструкторе класса.
Локальные (автоматические) объекты
Когда выброшено исключение, начинается разматывание стека с вызовом необходимых деструкторов. Однако деструкторы в этом случае вызываются только для полностью конструированных локальных объектов. Это означает, что если исключение выброшено в конструкторе объекта, для самого этого объекта деструктор вызван не будет. Будут вызваны только деструкторы его элементов-объектов и базовых классов. Поэтому, если объект содержал уже к этому времени указатели, например, на выделенную динамическую память, она освобождаться не будет. Возникнет утечка памяти.
Рассмотрите такой пример:
Листинг 12.4. Исключение в конструкторе
/////////////////////////////////////////////////////
// Construct.срр: Исключение в конструкторе. //
#inciude <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma hdrstop
#include <condefs.h>
void* operator new[](size_t size)
// Глобальная new[].
{
printf("Global new[].\n");
return malloc(size);
}
void operator delete[](void *p) // Глобальная delete[].
{
printf("Global delete[].\n");
free (p) ;
}
class Hold { // Класс, содержащий динамический массив char. char *ptr;
public:
Hold(char *str) // Конструктор преобразования из char*.
{
printf("Constructor.\n") ;
ptr = new char[strlen(str)+1] ;
strcpy(ptr, str) ;
// printf("Constructor: throwing exception...\n");
// throw "Exception!";
} ~Hold() // Деструктор.
{
printf("Destructor.\n") ;
delete [ ] ptr;
}
void Show() // Распечатка строки.
{
printf("My contents: %s\n", ptr);
} };
int main() {
try {
Hold h = "Some string."; // Попытка конструировать
// объект. h.Show() ;
} catch(char *str) {
printf("Message caught: %s\n", str);
}
printf("Exiting main...\n");
return 0;
}
Программа создает локальный в try-блоке объект класса Hold. Строка в конструкторе, выбрасывающая исключение, пока закомментирована, и программа выводит:

Часто для обработки исключительных ситуаций используются классы, специально предназначенные для этой цели. Главное в управлении исключениями — отыскать нужный обработчик, а это делается путем сопоставления типа выброшенного объекта с типами, объявленными в обработчиках. Поэтому иногда для исключений определяют совершенно “пустые” классы, однако с уникальными именами.
Классы исключений программы могут быть организованы в иерархическую структуру. Схожие типы исключений объявляются в качестве производных одного и того же базового класса, являющегося их обобщением. Используя полиморфные механизмы, можно перехватывать только указатель или ссылку на базовый класс; полиморфизм обеспечит адекватную обработку исключения любого производного класса. Вот примерная схема:
class GenericFault { // Обобщенная ошибка.
public: virtual void Report ();
//
// Конкретные типы ошибок...
//
class OpenError: public GenericFault {
public:
void Report();
}
class BadHeader: public GenericFault { public:
void Report ();
class BadRecord: public GenericFault ( public:
void Report () ;
}
int main() {
try {
}
catch(GenericFault &err) { err.Report () ;
}
return 0;
}
Следующие параграфы посвящены некоторым механизмам, которые нужно хорошо понимать, чтобы эффективно пользоваться возможностями управления исключениями.
Системные или процессорные исключения вроде деления на ноль невозможно обработать, пользуясь только механизмом исключений языка C++. Эти исключения сразу перехватываются операционной системой. Тем не менее, C++Builder поддерживает уже упоминавшееся в 4-й главе структурированное управление исключениями (SEH — Structured Exception Handling}, реализованное первоначально в качестве интегрированной части Windows NT и позволяющее работать с процессорными исключениями.
Исключения могут генерироваться или, как принято говорить в C++, выбрасываться либо исполнительной системой C++, стандартными функциями и т. д., либо самим программистом с помощью оператора throw. Он состоит из ключевого слова throw, за которым следует необязательное выражение.
Throw с операндом
Выражение, следующее за ключевым словом throw, можно рассматривать как фактический параметр (аргумент) в вызове функции, хотя здесь круглые скобки не обязательны. Обычно это константа или переменная, тип которой может быть любым, как встроенным, так и пользовательским. Тип операнда определяет обработчик, который будет вызван при исполнении оператора throw. На данный момент мы уже знаем достаточно, чтобы посмотреть законченный пример.
Листинг 12.1. Программа, демонстрирующая простейшие исключения
////////////////////////////////////////////////////////////////////
// SimpTypes.срр: Перехват простых исключений.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
int main () (
double d = 1.0;
for (int i=0; i<4; i++) { . try {
cout << endl<< "Entering the try-block..." <<end1;
switch (i) { case 0:
throw "Throwing an exception of char*"; // Выбросить
// строку. case 1:
throw 5; // Выбросить
// целое.
default:
throw d; // Выбросить double. }
// Следующий оператор исполняться не будет
// из-за исключений.
cout<< "In the „try-block after all exceptions..." << endl;
} // Конец try-блока.
catch(int 1) { // Обработчик int.
cout << "Int thrown: " << 1 << endl;
} catch(char* str) { // Обработчик char*.
cout << "String thrown: " << str << endl;
} catch (...) { // Для всего остального.
cout << "An unknown type thrown."<< "Program will.terminate." << endl;
cin.ignore () ;
return -1; // Завершить программу. }
cout<< "End of the loop."<< endl;
} // Конец цикла.


Общий синтаксис обработки исключения в C++ такой:
try { // Начало "пробного блока".
throw выражение; / / "Выбрасывание" исключения.
} catch(тип переменная) { // Заголовок обработчика для <типа>.
тело_обработчика) [catch ...] // Возможно, обработчики других типов.
Теперь мы в деталях рассмотрим элементы этой конструкции.
Если не удается найти подходящий обработчик исключения в списке текущего пробного блока, происходит переход на более высокий уровень, т. е. к списку обработчиков try-блока, -непосредственно включающего текущий. Если такой, конечно, имеется.
Если обработчика для данного исключения в программе не находится вообще, оно считается неуправляемым. В этом случае вызывается функция terminate () . Она, в свою очередь, вызывает функцию abort () , которая аварийно завершает программу.
Можно установить свою собственную процедуру завершения с помощью функции set_terminate () ; прототип ее находится в except, h:
typedef void(_RTLENTRY *terminate_function) ();
terminate_function _RTLENTRY set_terminate(terminate function);
Как и другие подобные функции, она возвращает адрес предыдущей процедуры завершения. Процедура завершения не может ни возвратить управление, ни выбросить исключение.
Следующая программа демонстрирует некоторые моменты вышесказанного. Ее вывод показан на рис. 12.2.
Листинг 12.3. Поиск обработчиков и неуправляемые исключения
///////////////////////////////////////////////////////////
//Unhandled. срр: Прохдедура для неуправляемых исключений.
//
#include <iostream.h>
#include <except.h>
#pragma hdrstop
#include <condefs.h>
class Dummy {}; // Пустой класс исключения.
void FuncB(int f) {
if (!f) { cout << "FuncB: throwing int..." << endl;
throw 7;
} else {
cout<< "FuncB: throwing char*..."<< endl;
throw "Exception!";
} }
void FuncA(int f)
{
try {
FuncB(f);
} catch(char* str) { // Обработчик выбрасывает Dummy.
cout << "FuncA: char* caught. Rethrowing Dummy..."<< endl;
Dummy d;
throw d;
} }
void MyTerminate() // Новая процедура завершения. {
cout << "Termination handler called..." << endl;
abort ();
}
int main() {
set_terminate(MyTerminate); // Установка процедуры
// завершения.
for (int j=0; j<2; j++) { try {
FuncA(j) ;
} catch(int k) {
cout << "Main: int caught - " << k << endl;
} }
// Следующие операторы исполняться не будут... cout “ "Exiting main..." “ endl;
return 0;
}

Рис.12.2 Выполнение программы Unhandled
Тело пробного блока в main () выполняется два раза. Имеется вложенный пробный блок в FuncA () . На первом проходе FuncB () выбрасывает int, для которого нет обработчика во внутреннем блоке и потому перехватываемое во внешнем пробном блоке, т. е. в main О . На втором проходе выбрасывается строка, которая перехватывается в FuncA () . Обработчик сам выбрасывает исключение Dummy — неуправляемое, поэтому вызывается установленная пользователем процедура завершения.
В библиотеке C++ имеется несколько предопределенных классов исключений. В следующих параграфах мы рассмотрим некоторые из них.
xmsg
Класс xmsg предназначен для передачи строковых сообщений об исключениях. Он объявлен в заголовке except, h:
class EXPCLASS xmsg : public std::exception
{ public:
xmsg(const std::string &msg);
xmsg(const xmsg &);
virtual ~xmsg() throw ();
xmsg & operator=(const xmsg &) ;
virtual const char * what() const throw ();
const std::string & why() const;
void raise () throw(xmsg);
private:
std::string *str;
};
Применять класс xmsg очень просто:
#include <iostream.h>
#include <except.h>
#pragma hdrstop
#include <condefs.h>
int main() {
try {
xmsg X("Exception!");
throw X;
// или
//
X.raise () ;
}
catch(xmsg Smsg) {
cout << "Caught in main: " << msg.why() << end1;
}
return 0;
}
В классе имеется функция-элемент raise (), посредством которой объект выбрасывает сам себя. Ею можно воспользоваться вместо оператора throw. Функция why () возвращает стандартную строку с сообщением, записанным в объект.

Класс xmsg считается устаревшим. Теперь в стандартной библиотеке C++ определяется ряд классов (производных от exception, как и xmsg), организованных иерархически. По сути они ничем друг от друга не отличаются; данные им имена ничего особенного не значат. Вот эти классы: class logic_error public exception class domain_error public logic_error class invalid argument public logic_error class length_error public logic_error class out_of_range public logic_error class runtime error public exception class range error public runtime error class overflow_error public runtime error class underflow error public runtime error
Как видите, logic_error и runtime_error — производные от exception, а все остальные — от первых двух. Эти классы имеют конструктор, принимающий ссылку на стандартную строку, виртуальный деструктор и виртуальную функцию-элемент what (), которая возвращает указатель на константную строку С. Вот пример:

Наибольший интерес для нас представляют исключения, связанные с процессором. Их, как уже говорилось, нельзя обрабатывать стандартными средствами C++. Для этих исключений в заголовке winbase.h определен ряд символических констант. Вот некоторые из них:
EXCEPTION_ACCESS_VIOLATION
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
EXCEPTION_FLT_INEXACT_RESULT
EXCEPTION_FLT_INVALID_OPERATION
EXCEPTION_FLT_OVERFLOW
EXCEPTION_FLT_STACK_CHECK
EXCEPTION_FLT_UNDERFLOW
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_PRIV_INSTRUCTION
EXCEPT ION_IN_PAGE_ERROR
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_NONCONTINUABLE_EXCEPTION
EXCEPTION_STACK_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_GUARD_PAGE
Ниже показан пример, в котором имитируется нарушение доступа путем разыменования нулевого указателя. Функция фильтра детектирует эту ошибку, и обработчик исключения выводит соответствующее собщение:
///////////////////////////////////////////////////
// Access.cpp: Применение SEH для перехвата
// системных исключений.
//
#include <except.h>
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
static int xfilter(EXCEPTION_POINTERS *info)
{
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_EXECUTE_HANDLER;
else
return EXCEPTION_CONTINUE_SEARCH;
}
int main () {
try (
int *p = NULL;
*P = -1;
}
_except(xfilter(GetExceptionInformation())) { cerr << "Exception Access Violaton caught..." << endl;
exit(l);
} cout<<"Normal exit..." << endl;
return 0;
}
Для справки приведем описание структуры exception_pointers:
struct EXCEPTION_POINTERS {
EXCEPTION_RECORD *ExceptionRecord;
CONTEXT *Context;
};
Struct EXCEPTION_RECORD { DWORD ExceptionCode;
DWORD ExceptionFlags;
struct EXCEPTION_RECORD *ExceptionRecord;
void *ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

Как вы, возможно, помните, в языке С (расширенном) имеются две структуры управления исключениями. Это обработка кадра (__try/_except) и обработка завершения (_try/_finally).
Обработки завершения мы здесь касаться не будем и вообще не будем в подробностях рассматривать работу со структурированными исключениями в C++, поскольку нас прежде всего интересуют здесь системные исключения, не поддерживаемые стандартным C++. Вместо ключевого слова _try в коде C++ принято писать просто try, как и для встроенных исключений языка. Таким образом, мы будем пользоваться только конструкцией try/_except.
Итак, синтаксис кадровой структуры управления имеет вид:
try
{
<тело защищенного блока> }
_except(<выражение-фильтр>) {
<блок обработки кадра>. }
В программе можно одновременно использовать управление исключениями C++ и кадрированную обработку исключений. Их структуры могут быть вложенными.
Фильтры
Фильтрующее выражение должно принимать одно из трех значений:
EXCEPTION_EXECUTE_HANDLER
EXCEPTION_CONTINUE_SEARCH
EXCEPTION_CONTINUE_EXECUTION
Они соответствуют исполнению данного обработчика, продолжению поиска (стек разматывается, и поиск обработчика переходит на более высокий уровень) и продолжению выполнения программы с той точки, где было возбуждено исключение.
В выражении фильтра можно вызвать функцию GetExceptionInformation() или GetExceptionCode () . Первая из них возвращает указатель на структуру EXCEPTION_POINTERS, которая содержит детальную ин-
формацию об исключении; вторая возвращает только кодовое значение для исключения. Чаще всего в качестве выражения фильтра используют функцию, аргументом которой является значение, возвращаемое одной из этих двух функций.

Сама функция фильтра не может вызывать GetExceptionInformation () или GetExceptionCode (). Значения, возвращаемые этими функциями, должны передаваться ей в аргументах.
Точно так же и блок обработчика не может вызывать данные функции. Функция фильтра должна скопировать необходимую информацию в такое место, где она будет доступна для обработчика исключения.
Вот пример простейшей функции фильтра:
int MyFilter(int code) {
if (code == EXCEPTION_ACCESS_VIOLATION) return EXCEPTION_EXECUTE_HANDLER;
else
return EXCEPTION_CONTINUE_SEARCH;
}
try {
}
_except(MyFilter(GetExceptionCode())) {
}
В определении функции можно указать, исключения какого типа она может выбрасывать. Спецификация исключений для функции выглядит так:
<тип> FuncName(<список параметров>) throw([<тип>
[, <тип> ...]])
{
<тело функции>
}
Тем самым мы сообщается, что функция может выбрасывать только типы, перечисленные в списке после ключевого слова throw. Если этот список пустой, то функция вообще не должна выбрасывать никаких исключений.
Обработка непредвиденных исключений
Однако то, какие исключения функция прямо или косвенно выбрасывает на самом деле, выясняется только во время выполнения. Компилятор не выдает никаких ошибок или предупреждений на этот счет. Если функция, снабженная спецификацией исключений, выбрасывает непредвиденное, т. е. не указанное в спецификации, исключение, вызывается функция unexpected () . По умолчанию последняя просто вызывает terminate () . Вы можете, тем не менее, указать свою собственную функцию, которая должна активироваться при появлении непредвиденных исключений, вызвав set_unexpected (). Прототип ее находится в файле except.h (не обращайте внимания на _RTLENTRY; он расширяется в _cdecl):
typedef void (_RTLENTRY *unexpected_function)();
…
unexpected_function _RTLENTRY set_unexpected(unexpected_function);
Возвращается указатель на предыдущую функцию обработки. Процедура для непредвиденных исключений не должна возвращать управление. Она может либо аварийно завершить программу, либо выбросить исключение.
Грамотно организованная, устойчивая программа должна справляться с нестандартными ситуациями, встречающимися в реальной работе с реальными данными. Такой нестандартной ситуацией может быть, например, ошибка пользователя при вводе данных или нарушение структуры некоторого файла. В языках, предшествующих C++, подобные проблемы решались с помощью глобальных “флагов ошибки” или приписыванием определенному значению, возвращаемому функцией, специального смысла “индикатора ошибки”. Язык C++ вводит понятие управления исключениями, т. е. специальных средств изменения программного потока управления с целью обработки нестандартных, непредвиденных или ошибочных ситуаций, возникающих в процессе работы.
В главе 4 мы вкратце упоминали о т. н. структурированной обработке исключений (SEH) в языке С; она реализуется в C++ Builder при посредстве нестандартных ключевых слов _try, _except и _finally. Ее принципы заимствованы, на самом деле, из операционной системы Windows NT; в NT это средства, встроенные в систему.
В C++ средства обработки исключений встроены непосредственно в язык. Ключевые слова, связанные с данным аспектом языка, следующие: try, catch и throw.
Из достоинств обработки ошибок с использованием исключений, по сравнению с традиционными методами, можно назвать следующие:
устранение глобальных переменных;
увеличение возможностей отладки благодаря тому, что исключения являются составной частью языка;
невозможность игнорирования ошибок и продолжения программы так, как будто бы ничего не случилось.
Недостаток обработки исключений состоит, на наш взгляд, в том, что приходится вводить дополнительные уровни операторных скобок; код делается более громоздким. Но это в любом случае с лихвой компенсируется ее достоинствами.
Следует заметить, что, в отличие от модели SEH, при генерировании исключения в C++ невозможно продолжить выполнение программы с того самого места, где оно возникло.
Помимо упоминавшегося выше флажка Location information, в группе Exception handling страницы C++ диалога установок проекта находятся следующие флажки:
Enable exceptions — при сброшенном флажке исключения запрещены, и компилятор генерирует сообщение об ошибке, если встречает try-блок; соответствует ключу командной строки -х и по умолчанию установлен.
Destructor cleanup — если установлен, то при разматывании стека вызываются деструкторы локальных автоматических объектов; по умолчанию установлен. Соответствующий ключ командной строки — -xd.
Fast exception prolog — при установленном флажке ряд служебных функций, ответственных за обработку исключений, расширяются как inline; по умолчанию сброшен. Ключ командной строки -xf.