/ X(int); }; class Y { /* ... */ Y(X); }; Y a = 1; // недопустимо: преобразование Y(X(1)) // не применяется R.12.3.2 Функции преобразования Функция-член класса X, имя которой имеет вид, имя-функции-преобразования: operator имя-типа-преобразования имя-типа-преобразования: список-спецификаций-типа opt операция-ptr opt задает преобразование из типа X в тип, определяемый конструкцией имя-типа-преобразования. Такие функции-члены называются функциями преобразования. В конструкции список-спецификаций-типа нельзя описывать классы, перечисления и имена-typedef, а также нельзя задавать типы формальных параметров и тип возвращаемого значения. Приведем пример: class X { // ... public: operator int(); }; void f(X a) { int i = int(a); i = (int)a; i = a; } Здесь во всех трех операторах присваиваемое значение будет преобразовываться с помощью функции X::operator int(). Пользовательские преобразования не ограничиваются только использованием в присваивании и инициализации, например: void g(X a, X b) { int i = (a) ? 1+a : 0; int j = (a&&b) ? a+b : i; if (a) { // ... } } Операции преобразования наследуются. Функции преобразования могут быть виртуальными. К данному значению неявно применяется не более одного пользовательского преобразования (с помощью конструктора или функции преобразования), например: class X { // ... public: operator int(); }; class Y { // ... public: operator X(); }; Y a; int b = a; // недопустимо: преобразование // a.operator X().operator int() не применяется int c = X(a); // нормально: a.operator X().operator int() Пользовательские преобразования осуществляются неявно только при условии их однозначности. Функция преобразования производного класса не закрывает функцию преобразования базового класса, если только они не преобразуют к одному и тому же типу, например: class X { public: // ... operator int(); }; class Y : public X { public: // ... operator void*(); }; void f(Y& a) { if (a) { // ошибка: неоднозначность } } R.12.4 Деструкторы Деструктором называется функция-член класса cl с именем ~cl, она используется для уничтожения значений типа cl непосредственно перед уничтожением объекта, содержащего их. Деструктор не имеет формальных параметров и для него нельзя задать тип возвращаемого значения (даже void). Нельзя применять операцию взятия адреса для деструктора. Можно вызывать деструктор для объектов со спецификацией const или volatile, но сам деструктор нельзя описывать с этими спецификациями ($$R.9.3.1). Деструктор не может быть и статическим. Деструкторы не наследуются. Если базовый класс или член имеют деструктор, а сам производный класс - нет, то создается стандартный деструктор, который вызывает деструкторы базовых классов и членов производного класса. Такие созданные деструкторы имеют спецификацию public. Тело деструктора выполняется прежде деструкторов для объектов, являющихся членами. Деструкторы для нестатических объектов, являющихся членами, выполняются прежде, чем деструкторы для базовых классов. Деструкторы для невиртуальных базовых классов выполняются прежде, чем деструкторы для виртуальных базовых классов. Деструкторы для невиртуальных базовых классов выполняются в порядке, обратном их описанию в производном классе. Деструкторы виртуальных базовых классов выполняются в порядке, обратном появлению их при обходе снизу и слева-направо ацикличного направленного графа базовых классов. Здесь "слева-направо" означает порядок появления имен базовых классов, который был при описании их в производном классе. Деструкторы для элементов массива вызываются в порядке, обратном вызовам при их построении. Деструктор может быть виртуальным. В деструкторе можно вызывать функцию-член, см. $$R.12.7. Объект класса с деструктором не может быть членом объединения. Деструкторы вызываются неявно в следующих случаях: (1) когда исчезают из области видимости объекты auto ($$R.3.5) или временные объекты ($$R.12.2, $$R.8.4.3); (2) при завершении программы ($$R.3.4) для построенных статических объектов ($$R.3.5); (3) благодаря обращению к операции delete ($$R.5.3.4) для объектов, созданных с помощью операции new ($$R.5.3.3); (4) при явном вызове. Когда деструктор вызывается операцией delete, то он освобождает память для самого большего из производных классов ($$R.12.6.2) того объекта, который использовал операцию delete() ($$R.5.3.4), например: class X { // ... public: X(int); ~X(); }; void g(X*); void f() // общий случай { X* p = new X(111); // размещение и инициализация g(p); delete p; // освобождение и удаление } Явные вызовы деструкторов применяются редко. Примером этого может служить вызов деструктора для объектов, созданных в некоторой определенной адресной области с помощью операции new. Размещение объектов в определенном адресном пространстве и последующее уничтожение их может потребоваться для использования специфических возможностей аппаратуры и для правильного функционирования оперативной памяти. Приведем пример: void* operator new(size_t, void* p) { return p; } void f(X* p); static char buf[sizeof(X)]; void g() // редкий, специальный случай { X* p = new(buf) X(222); // размещение в buf[] и инициализация f(p); p->X::~X(); // удаление } Обозначения, использованные для явного вызова деструктора, можно использовать для имени любого простого типа, например, int* p; // ... p->int::~int(); Использование такой записи для типа, у которого нет деструктора, проходит бесследно. Допуская такую запись, мы разрешаем пользователям писать программу, не задумываясь над тем, есть ли данного типа деструктор. R.12.5 Свободная память Когда создается объект с помощью операции new, для получения свободной памяти вызывается (неявно) функция operator new() ($$R.5.3.3). Если функция operator new() не может выполнить запрос, она возвращает 0. В классе X функция X::operator new() является статическим членом, даже если она не описана явно как static. Первый ее параметр должен иметь тип size_t, - зависящий от реализации целочисленный тип, который определен в стандартном заголовочном файле <stddef.h>, и она должна возвращать значение типа void*, например: class X { // ... void* operator new(size_t); void* operator new(size_t, Arena*); }; Правила выбора подходящей функции operator new() обсуждаются в $$R.5.3.3. В классе X функция X::operator delete() является статическим членом, даже если она не описана явно как static. Первый ее параметр должен быть типа void* и можно добавлять второй параметр типа size_t. Она не может возвращать какое-либо значение и тип возвращаемого значения должен быть void, например: class X { // ... void operator delete(void*); }; class Y { // ... void operator delete(void*, size_t); }; В каждом классе можно описать только одну функцию operator delete(), значит эта функция не может быть перегруженной. Глобальная функция operator delete() имеет единственный параметр типа void*. Если функция описана с двумя формальными параметрами, она вызывается с двумя параметрами, второй из которых показывает размер удаляемого объекта. Передаваемый размер определяется с помощью деструктора (если он есть) или по типу (статическому) указателя на удаляемый объект. Операция пройдет корректно, если тип указателя, заданного как фактический параметр, будет совпадать с типом объекта (а не будет, к примеру, просто типом указателя на базовый класс) или, если этот тип является типом указателя на базовый класс с виртуальным деструктором. Для массивов объектов типа класс используются глобальные функции operator new() и operator delete() ($$R.5.3.3, $$R.5.3.4). Поскольку функции X::operator new() и X::operator delete() статические, они не могут быть виртуальными. Функция operator delete(), которая вызывается из деструктора для освобождения памяти, выбирается по обычным правилам областей видимости, например: struct B { virtual ~B(); void* operator new(size_t); void operator delete(void*); }; struct D : B { ~D(); void* operator new(size_t); void operator delete(void*); }; void f() { B* p = new D; delete p; } В этом примере память для объекта класса D выделяется с помощью D::operator new(), а благодаря наличию виртуального деструктора, освобождается с помощью D::operator delete(). R.12.6 Инициализация Объект класса без конструкторов, без частных или защищенных членов, без виртуальных функций и без базовых классов можно инициализировать с помощью списка инициализаторов ($$R.8.4.1). Объект класса с конструктором должен инициализироваться или иметь стандартный конструктор ($$R.12.1). Стандартный конструктор используется для объектов, которые не проходят явной инициализации. R.12.6.1 Явная инициализация Объекты классов с конструкторами ($$R.12.1) можно инициализировать списком выражений, заключенным в скобки. Этот список считается списком фактических параметров для вызова конструктора, производящего инициализацию. Иначе, в качестве инициализатора задается с помощью операции = одно значение. Оно используется как фактический параметр для конструктора копирования. Обычно можно обойтись без вызова конструктора копирования, например: class complex { // ... public: complex(); complex(double); complex(double,double); // ... }; complex sqrt(complex,complex); complex a(1); // инициализация вызовом // complex(double) complex b = a; // инициализация копированием `a' complex c = complex(1,2); // конструктор complex(1,2) // вызывается complex(double,double) // и копируется в `c' complex d = sqrt(b,c); // вызывается sqrt(complex,complex), // результат копируется в `d' complex e; // инициализация вызовом конструктора complex f = 3; // complex(3), вызывается // complex(double) и результат // копируется в `f' Перегрузка операции присваивания = не оказывает влияние на инициализацию. Инициализация, происходящая при передаче фактических параметров и при возврате из функции, эквивалентна инициализации вида T x = a; Инициализация, происходящая в выражении операции new ($$R.5.3.3) и при инициализации базовых классов и членов, эквивалентна инициализации вида T x(a); Для массивов объектов класса с конструкторами используются при инициализации ($$R.12.1) конструкторы как и для одиночных объектов. Если оказалось, что инициализаторов в списке меньше, чем элементов массива, используется стандартный конструктор ($$R.12.1). Если его нет, список инициализаторов должен быть полным. Приведем пример: complex cc = { 1, 2 }; // ошибка: необходимо // использовать конструктор complex v[6] = { 1,complex(1,2),complex(),2 }; Здесь v[0] и v[3] инициализируются значением complex::complex(double), v[1] инициализируется complex::complex(double,double), а v[2], v[4] и v[5] инициализированы complex::complex(). Объект класса M моет быть членом класса X в одном из следующих случаев: (1) M не имеет конструктора; (2) M имеет стандартный конструктор; (3) X имеет конструктор и каждый из них задает инициализатор-ctor ($$R.12.6.2) для члена M. В случае 2 при создании составного объекта вызывается стандартный конструктор. Если член составного объекта имеет деструктор, то он вызывается при уничтожении составного объекта. Конструкторы для нелокальных статических объектов вызываются в том порядке, в каком они идут в тексте программы, деструкторы вызываются в обратном порядке, см. также $$R.3.4, $$R.6.7, $$R.9.4. R.12.6.2 Инициализация членов и базовых классов В определении конструктора можно задать инициализацию прямых базовых классов и членов, не наследуемых из базовых классов. Это особенно полезно для тех объектов, констант и ссылок, для которых различаются семантики присваивания и инициализации. Конструкция инициализатор-ctor имеет вид инициализатор-ctor: : список-инициализаторов-членов список-инициализаторов-членов: инициализатор-члена инициализатор-члена , список-инициализаторов-члена инициализатор-члена: полное-имя-класса ( список-выражений opt ) идентификатор Список параметров используется для инициализации нестатических членов или объектов базового класса. Это единственный способ инициализации нестатических членов, являющихся ссылками или объектами типа const, например: struct B1 { B1(int); /* ... */ }; struct B2 { B2(int); /* ... */ }; struct D : B1, B2 { D(int); B1 b; const c; }; D::D(int a) : B2(a+1), B1(a+2), c(a+3), b(a+4) { /* ... */ } D d(10); В начале инициализируются базовые классы в порядке их описания (независимо от порядка инициализаторов-членов), затем по той же схеме инициализируются члены, и наконец выполняется тело D::D() ($$R.12.1). Порядок описания выдерживается для того, чтобы гарантировать, что вложенные объекты и члены будут уничтожаться в порядке, обратном их инициализации. Особый случай представляют виртуальные базовые классы. Они создаются прежде, чем любой невиртуальный базовый класс и в том же порядке, в каком появляются при обходе снизу и слева-направо ацикличного направленного графа базовых классов. Порядок "слева-направо" - это тот, в котором имена базовых классов задаются при описании в производном классе. Полным называется объект, который не является вложенным объектом, представляющим некоторый базовый класс. Класс такого объекта называют наибольшим производным классом объекта. Все вложенные объекты виртуальных базовых классов инициализируются с помощью конструктора наибольшего производного класса. Если в конструкторе наибольшего производного класса не задан инициализатор-члена для виртуального базового класса, тогда этот виртуальный базовый класс должен иметь стандартный конструктор,либо не иметь никакого конструктора. Всякий инициализатор-члена для виртуального базового класса, заданный не в конструкторе класса полного объекта, игнорируется. Приведем пример: class V { public: V(); V(int); // ... }; class A : public virtual V { public: A(); A(int); // ... }; class B : public virtual V { public: B(); B(int); // ... }; class C : public A, public B, private virtual V { public: C(); C(int); // ... }; A::A(int i) : V(i) { /* ... */ } B::B(int i) { /* ... */ } C::C(int i) { /* ... */ } V v(1); // use V(int) A a(2); // use V(int) B b(3); // use V() C c(4); // use V() Инициализатор-члена вычисляется в области видимости конструктора, в котором он появился. Например, в следующем фрагменте class X { int a; public: const int& r; X()::r(a) { } }; X::r инициализируется для каждого объекта класса X ссылкой на X::a. R.12.7 Конструкторы и деструкторы В конструкторах и деструкторах можно вызывать функцию-член. Отсюда следует, что можно вызывать (непосредственно или косвенно) виртуальные функции. Вызываемая функция должна быть определена в классе самого конструктора или деструктора или в базовых классах, но не должна быть функцией, которая их подавляет в производном классе. Этим обеспечивается то, что еще несозданные объекты не будут использованы при выполнении конструктора или деструктора. Рассмотрим пример: class X { public: virtual void f(); X() { f(); } // вызов X::f() ~X() { f(); } // вызов X::f() }; class Y : public X { int& r; public: void f() { r++; // беда, если `r' не инициализировано } Y(int& rr) ::r(rr) { } }; Результат непосредственного или косвенного вызова из конструктора чистой виртуальной функции для инициализируемого объекта неопределен, если только явно не использовано уточнение имени функции ($$R.10.3). R.12.8 Копирование объектов класса Объекты класса могут копироваться двумя способами: либо присваиванием ($$R.5.17), либо инициализацией ($$R.12.1, $$R.8.4), которая может происходить при передаче параметров ($$R.5.2.2) или результата функции ($$R.6.6.3). Для класса X эти две операции концептуально реализуются как операция присваивания и конструктор копирования ($$R.12.1). В программе можно определить или одну из них, или обе. Если пользователь не определил их в программе, то они будут для всех членов класса X определяться соответственно как присваивание по членам и инициализация по членам. Если все базовые классы и все члены класса X имеют конструктор копирования, в котором допустимы в качестве параметра объекты типа const, то порождаемый конструктор копирования для X будет иметь единственный параметр типа const X& и записываться так: X::X(const X&) Иначе, у него будет единственный параметр типа X&: X::X(X&) и инициализация копированием объектов типа const класса X будет невозможна. Аналогично, если все базовые классы и члены класса X имеют операцию присваивания, допускающую параметры типа const, тогда порождаемая для X операция присваивания будет иметь единственный параметр типа const X& и записываться так: X& X::operator=(const X&) Иначе, у нее будет единственный параметр типа X&: X& X::operator=(X&) и присваивание копированием объектов класса X типа const будет невозможно. Стандартная операция присваивания возвращает ссылку на объект, который нужно было копировать. Объекты, представляющие виртуальные базовые классы, будут инициализироваться только один раз с помощью порождаемого конструктора копирования. Объекты, представляющие виртуальные базовые классы, допускают присваивания им только один раз с помощью порождаемой операции присваивания. Присваивание по членам и инициализация по членам означают следующее: если класс X имеет в качестве члена класс M, для реализации присваивания и инициализации члена используются операции присваивания в M и конструктор копирования M соответственно. Если класс имеет член типа const, или член, являющийся ссылкой, или член или базовый класс такого класса, где функция operator=() является частной, то для него стандартная операция присваивания не может быть создана. Аналогично, если член или базовый класс класса M имеет частный конструктор копирования, то стандартный конструктор копирования для такого класса не может быть создан. Пока не появится необходимость в определении, стандартные присваивание и конструктор копирования будут только описаны (т.е. не будет создано тело функции). Иными словами, функция X::operator=() будет порождена только тогда, когда нет явного описания операций присваивания, а объект класса X присваивается объекту класса X или объекту класса, производного от X, или вычисляется адрес функции X::operator=(). Аналогичная ситуация с инициализацией. Если присваивание и конструктор копирования описаны неявно, то они будут общими функциями-членами и операция присваивания для класса X определяется таким образом, что ее результатом является ссылка типа X& на объект, которому присваивают. Если в классе X есть функция X::operator=(), параметром которой является сам класс X, то стандартное присваивание не будет порождаться. Если в классе определен какой-либо конструктор копирования, то стандартный конструктор копирования не будет порождаться. Приведем пример: class X { // ... public: X(int); X(const X&, int = 1); }; X a(1); // вызов X(int) X b(a,0); // вызов X(const X&,int) X c = b; // вызов X(const X&,int) Присваивание объектов класса X определяется через функцию X::operator=(const X&). Это означает ($$R.12.3), что объекты производного класса можно присваивать объектам общего базового класса, например: class X { public: int b; }; class Y : public X { public: int c; }; void f() { X x1; Y y1; x1 = y1; // нормально y1 = x1; // ошибка } В этом примере y1.b присваивается x1.b, а x1.c не копируется. Копирование одного объекта в другой с помощью стандартной операции копирования или стандартного конструктора копирования не изменяет структуру обоих объектов. Приведем пример: struct s { virtual f(); // ... }; struct ss : public s { f(); // ... }; void f() { s a; ss b; a = b; // на самом деле выполняется a.s::operator=(b) b = a; // ошибка a.f(); // вызов s::f b.f(); // вызов ss::f (s&)b = a; // присваивание a b // на самом деле выполняется ((s&)b).s::operator=(a) b.f(); // все еще вызов ss::f } Вызов a.f() приведет к вызову s::f() (как и должно быть для объекта класса s ($$R.10.2)), а вызов b.f() приведет к вызову ss::f() ( как и должно быть для объекта класса ss). R.13 Перегрузка Говорят, что имя перегружено, если для него задано несколько различных описаний функций в одной области видимости. При использовании имени выбор правильной функции производится путем сопоставления типов формальных параметров с типами фактических параметров, например: double abs(double); int abs(int); abs(1); // вызов abs(int) abs(1.0); // вызов abs(double) Поскольку при любом типе T и для самого T , для и T& допустимо одно и то же множество инициализирующих значений, функции, типы параметров которых различаются только использованием, или не использованием ссылки, не могут иметь одинаковые имена, например: int f(int i) { // ... } int f(int& r) // ошибка: типы функций { // недостаточно различны // ... } Аналогично, поскольку для любом типе T для самого T, const T и volatile T допустимо одно и то же множество инициализирующих значений, функции, типы параметров которых отличаются только указанной спецификацией, не могут иметь одинаковые имена. Однако, различить const T&, volatile T& и просто T& можно, поэтому допустимы определения функций с одним именем, которые различаются только в указанном отношении. Аналогично, допустимы определения функций с одним именем, типы параметров которых различаются только как типы вида const T*, volatile T* и просто T*. Не могут иметь одинаковые имена функции, которые отличаются только типом возвращаемого значения. Не могут иметь одинаковые имена функции-члены, одна из которых статическая, а другая нет ($$R.9.4). С помощью конструкции typedef не создаются новые типы, а только определяется синоним типа ($$R.7.1.3), поэтому функции, которые отличаются только за счет использования типов, определенных с помощью typedef, не могут иметь одинаковые имена. Приведем пример: typedef int Int; void f(int i) { /* ... */ } void f(Int i) { /* ... */ } // ошибка: переопределение f С другой стороны все перечисления считаются разными типами, и с их помощью можно различить перегруженные функции, например: enum E { a }; void f(int i) { /* ... */ } void f(E i) { /* ... */ } Типы параметров, которые различаются только тем, что в одном используется указатель *, а в другом массив [], считаются идентичными. Напомним, что для типа параметра важны только второй и последующие индексы многомерного массива ($$R.8.2.4). Подтвердим сказанное примером: f(char*); f(char[]); // идентично f(char*); f(char[7]); // идентично f(char*); f(char[9]); // идентично f(char*); g(char(*)[10]); g(char[5][10]); // идентично g(char(*)[10]); g(char[7][10]); // идентично g(char(*)[10]); g(char(*)[20]); // отлично от g(char(*)[10]); R.13.1 Сопоставление описаний Два описания функций с одинаковыми именами относятся к одной и той же функции, если они находятся в одной области видимости и имеют идентичные типы параметров ($$R.13). Функция-член производного класса относится к иной области видимости, чем функция-член базового класса с тем же именем. Рассмотрим пример: class B { public: int f(int); }; class D : public B { public: int f(char*); }; Здесь D::f(char*) скорее скрывает B::f(int), чем перегружает эту функцию. void h(D* pd) { pd->f(1); // ошибка: D::f(char*) скрывает B::f(int) pd->B::f(1); // нормально pd->f("Ben"); // нормально, вызов D::f } Функция, описанная локально, находится в иной области видимости, чем функция с файловой областью видимости. int f(char*); void g() { extern f(int); f("asdf"); // ошибка: f(int) скрывает f(char*) поэтому // в текущей области видимости нет f(char*) } Для разных вариантов перегруженной функции-члена можно задать разные правила доступа, например: class buffer { private: char* p; int size; protected: buffer(int s, char* store) { size = s; p = store; } // ... public: buffer(int s) { p = new char[size = s]; } }; R.13.2 Сопоставление параметров При вызове функции с данным именем происходит выбор из всех функций с этим именем, которые находятся в текущей области видимости, и для которых существуют преобразования типа, делающие вызов возможным. Выбирается та функция, которая наиболее соответствует фактическим параметрам. Она находится в области пересечения множеств функций, каждое из которых наиболее соответствуют вызову по данному фактическому параметру. Операция вызова считается допустимой, если в этом пересечении находится только один член. Функция, выбранная таким образом, должна более любой другой функции с тем же именем соответствовать вызову, хотя бы по одному из параметров (необязательно это будет один и тот же параметр для разных функций). В противном случае, вызов считается недопустимым. При сопоставлении параметров рассматривают функцию с числом стандартных значений параметров ($$R.8.2.6), равным n, как n+1 функций с различным числом параметров. При сопоставлении параметров нестатическую функцию-член рассматривают как функцию, имеющую дополнительный параметр, указывающий на объект, для которого вызывается функция. Этот дополнительный формальный параметр должен сопоставляться или с объектом, или с указателем на объект, заданными в явной операции вызова функции-члена ($$R.5.2.4), или же с первым операндом перегруженной функции operator ($$R.13.4). Для этого дополнительного параметра не используется никаких временных объектов, а для достижения сопоставления не производится никаких пользовательских преобразований типа. Если явно вызывается член класса X, используя указатель и операцию ->, то считается, что дополнительный параметр имеет тип const* X для членов типа const, volatile* X для членов типа volatile и X* для всех остальных членов. Если явно вызывается функция-член, используя объект и операцию ., а также, если вызывается функция для первого операнда перегруженной функции operator ($$R.9.4), то считается, что дополнительный параметр имеет тип: const X& для членов типа const, volatile X& для членов типа volatile и X& для всех остальных членов. Первый операнд для ->* и .* рассматривается так же, как и первый операнд для -> и . соответственно. Эллипсис в списке формальных параметров ($$R.8.2.5) может сопоставляться с фактическим параметром любого типа. Для данного фактического параметра допускается только такая последовательность преобразований типа, которая содержит не более одного пользовательского преобразования. Ее нельзя сократить, исключив одно или несколько преобразований, до последовательности, которая также приводит к типу, сопоставимому с типом рассматриваемого формального параметра. Такая последовательность преобразований называется наиболее соответствующей последовательностью. Например, последовательность int->float->double задает преобразование int в double, но ее нельзя назвать наиболее соответствующей последовательностью, поскольку в ней содержится более короткая последовательность int->double. Кроме описанных ниже случаев, следующие тривиальные преобразования типа T не влияют на свойство последовательности быть наиболее соответствующей: исходный тип тип результата T T& T& T T[] T* T(параметры) T(*)(параметры) T const T T volatile T T* const T* T* volatile T* Последовательности тривиальных преобразований, которые отличаются только порядком преобразований, считаются совпадающими. Отметим, что для функций с формальным параметром типа T, const T, volatile T, T&, const T& и volatile T& допустим фактический параметр из одно и того же множества значений. При необходимости для разделения последовательностей преобразований используются спецификации const и volatile, как описано в правиле [1] ниже. Для формального параметра типа T& требуется временная переменная в случаях, если: фактический параметр не является адресом, или имеет тип, отличный от T, в том числе тип volatile. Наличие такой переменной не влияет на сопоставление параметров. Однако, оно может повлиять на допустимость результата сопоставления, т.к. временную переменную нельзя использовать для инициализации ссылок, не являющихся const ($$R.8.4.3). Последовательности преобразований рассматриваются согласно следующим правилам: [1] Точное сопоставление. Последовательности из нуля или более тривиальных преобразований предпочтительнее любых других последовательностей. Из более сложных последовательностей наиболее предпочтительны те, в которых нет преобразований T* в const T*, T* в volatile T*, T& в const T& или T& в volatile T&. [2] Сопоставление со стандартными преобразованиями основных типов. Из последовательностей, не относящихся к [1], наиболее предпочтительны те, которые содержат только стандартные целочисленные преобразования ($$R.4.1), преобразования float в double и тривиальные преобразования. [3] Сопоставление с любыми стандартными преобразованиями. из последовательностей, не относящихся к [2], наиболее предпочтительны те, которые содержат только любые стандартные преобразования ($$R.4.1, $$R.4.2, $$R.4.3, $$R.4.4, $$R.4.5, $$R.4.6, $$R.4.7, $$R.4.8) и тривиальные преобразования. Для этих последовательностей если A является прямым или косвенным общим базовым для класса B, то преобразование B* в A* предпочтительнее преобразования B* в void* или const void*. Далее, если B является прямым или косвенным базовым классом для C, то предпочтительнее преобразование C* в B*, чем C* в A*, и предпочтительнее преобразование C& в B&, чем C& в A&. Иерархия классов выступает здесь критерий отбора преобразований указателя в член ($$R.4.8). [4] Сопоставление с пользовательскими преобразованиями. Из последовательностей, не относящихся к [3], наиболее предпочтительны те, которые содержат только пользовательские ($$R.12.3), стандартные ($$R.4) и тривиальные преобразования. [5] Сопоставление с эллипсисом. Последовательности, которые требуют сопоставления с эллипсисом, считаются наименее предпочтительными. Пользовательские преобразования выбирают, исходя из типа переменной, которая инициализируется или которой присваивается значение. class Y { // ... public: operator int(); operator double(); }; void f(Y y) { int i = y; // вызов Y::operator int() double d; d = y; // вызов Y::operator double() float f = y; // ошибка: неоднозначность } Стандартные преобразования ($$R.4) могут применяться к параметру, как до пользовательского преобразования, так и после него. struct S { S(long); operator int(); }; void f(long), f(char*); void g(S), g(char*); void h(const S&), h(char*); void k(S& a) { f(a); // f(long(a.operator int())) g(1); // g(S(long(1))) h(1); // h(S(long(1))) } Если для параметра требуется пользовательское преобразование, то не учитываются никакие стандартные преобразования, которые могут затрагивать этот параметр, например: class x { public: x(int); }; class y { public: y(long); }; void f(x); void f(y); void g() { f(1); // неоднозначность } Здесь вызов f(1) неоднозначен. Несмотря на то, что для вызова f(y(long(1))) требуется на одно стандартное преобразование больше, чем для вызова f(x(1)), второй вызов не является предпочтительным. Преобразования с помощью конструктора ($$R.12.1) и с помощью функции преобразования ($$R.12.3.2) равноправны. struct X { operator int(); }; struct Y { Y(X); }; Y operator+(Y,Y); void f(X a, X b) { a+b; // ошибка, неоднозначность: // operator+(Y(a), Y(b)) или // a.operator int() + b.operator int() } R.13.3 Адрес перегруженной функции Когда функция с некоторым именем используется без параметров, среди всех функций с таким именем в текущей области видимости выбирается единственная, которая точно соответствует назначению. Назначением может быть: инициализируемый объект ($$R.8.4); левая часть операции присваивания ($$R.5.17); формальный параметр функции ($$R.5.2.2); формальный параметр пользовательской операции ($$R.13.4); тип значения, возвращаемого функцией ($$R.8.2.5). Отметим, что если f() и g() являются перегруженными функциями, то для правильной интерпретации f(&g) или эквивалентного выражения f(g) нужно рассмотреть пересечение множеств выбора для f() и g(). Приведем пример: int f(double); int f(int); int (*pfd)(double) = &f; int (*pfi)(int) = &f; int (*pfe)(...) = &f; // ошибка: несоответствие типов Последняя инициализация ошибочна, не из-за неоднозначности, а потому, что не определено ни одной функции f() типа int(...). Отметим, что не существует никакого стандартного преобразования ($$R.4) указателя на функцию одного типа в указатель на функцию другого типа ($$R.4.6). В частности, даже если B является общим базовым классом D, две следующие инициализации недопустимы: D* f(); B* (*p1)() = &f; // ошибка void g(D*); void (*p2)(B*) = &g; // ошибка R.13.4 Перегруженные операции Перегружать можно большинство операций. имя-функции-оператор: operator операция операция: один из new delete + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] Две последние операции - это вызов функции ($$R.5.2.2) и индексация ($$R.5.2.1). Можно перегружать следующие (как бинарные, так и унарные) операции: + - * & Нельзя перегружать следующие операции: . .* :: ?: sizeof а также и специальные символы препроцессора # и ## ($$R.16). Обычно функции, задающие операции (функция-оператор) не вызываются явно, к ним обращаются для выполнения операций ($$R.13.4.1, $$R.13.4.2). Однако, к ним можно обращаться явно, например: complex z = a.operator+(b); // complex z = a+b void* p = operator new(sizeof(int)*n); Операции new и delete описаны в $$R.5.3.3 и $$R.5.3.4 и к ним не относятся перечисляемые ниже правила. Функция-оператор может быть функцией-членом или иметь по крайней мере один параметр типа класс или ссылка на класс. Нельзя изменить приоритет, порядок выполнения или число операндов операции, но можно изменить предопределенное назначение таких операций: =, унарная & и ,(запятой), если они применяются к объекту типа класс. За исключением функции operator=(), функция-оператор наследуется. Правила для operator=() даны в $$R.12.8. Эквивалентность некоторых операций над основными типами (например, ++a эквивалентно a+=1) может не сохраняться для таких же операций над классами. Для некоторых операций требуется, чтобы в случае использования основных типов операнд был адресом (например, для +=). Это требование может быть снято, если операция задана над классами. Перегруженная операция не может иметь стандартные значения параметров ($$R.8.2.6). Операции, которые явно не указаны в $$R.13.4.3-$$R.13.4.7, действуют как обычные унарные или бинарные операции, подчиняющиеся правилам, приведенным в $$R.13.4.1 или $$R.13.4.2. R.13.4.1 Унарные операции Префиксную унарную операцию можно задать с помощью нестатической функции-члена ($$R.9.3), без параметров или с помощью функции, не являющейся членом, с одним параметром. Таким образом, для всякой префиксной унарной операции @, выражение @x может интерпретироваться как x.operator@() или как operator@(x). Если описаны функции-операторы обоих видов, то какая из них будет использоваться при вызове, определяется правилами сопоставления параметров ($$R.13.2). Постфиксные унарные операции, такие как ++ и -- , объясняются в $$R.13.4.7. R.13.4.2 Бинарные операции Бинарную операцию можно задать с помощью нестатической функции-члена ($$R.9.3), имеющей один параметр, или с помощью функции, не являющейся членом, с двумя параметрами. Таким образом, для всякой бинарной операции @ выражение x@y может интерпретироваться как x.operator@(y) или как operator@(x,y). Если описаны функции-операторы обоих видов, то какая из них будет использоваться при вызове, определяется правилами сопоставления параметров ($$R.13.2). R.13.4.3 Присваивания Функция присваивания operator=() должна быть нестатической функцией-членом. Она не наследуется ($$R.12.8). Более того, если пользователь не определил для класса X функцию operator=, то используется стандартная функция operator=, которая определяется как присваивание по членам для класса X. X& X::operator=(const X& from) { // копирование по членам X } R.13.4.4 Вызов функции Вызов функции есть конструкция вида: первичное-выражение ( список-выражений opt ) Она считается бинарной операцией, в которой первичное-выражение представляет первый операнд, а список-выражений (возможно пустой), - второй операнд. Именем, задающим функцию, служит operator(), и вызов x(arg1,arg2,arg3) для объекта класса x интерпретируется как x.operator()(arg1,arg2,arg3). Функция operator() должна быть нестатической функцией-членом класса x. R.13.4.5 Индексация Индексация, определяемая как: первичное-выражение [ выражение ] считается бинарной операцией. Выражение с индексацией x[y] для объекта класса x интерпретируется как x.operator[](y). Функция operator[] должна быть нестатической функцией-членом класса x. R.13.4.6 Доступ к члену класса Доступ к члену класса определяется с помощью операции ->: первичное-выражение -> первичное-выражение Он считается унарной операцией. Для объекта класса x выражение x->m интерпретируется как (x.operator->())->m. Отсюда следует, что функция operator->() должна возвращать или указатель на класс, или ссылку на класс, или объект класса, для которого определена функция operator->(). Она должна быть нестатической функцией-членом класса. R.13.4.7 Инкремент и декремент Функция с именем operator++ и с одним параметром задает для объектов некоторого класса операцию префиксного инкремента ++. Функция с именем operator++ и с двумя параметрами задает для объектов некоторого класса операцию постфиксного инкремента ++. Для постфиксной операции ++ второй параметр должен быть типа int, и, когда в выражении встречается операция постфиксного инкремента, функция operator++ вызывается со вторым параметром, равным нулю. Приведем пример: class X { public: X operator++(); // префиксная ++a X operator++(int) // постфиксная a++ }; void f(X a) { ++a; // a.operator++(); a++; // a.operator++(0); a.operator++(); // явный вызов: действует как ++a; a.operator++(0); // явный вызов: действует как a++; } Префиксные и постфиксные операции декремента -- определяются аналогичным образом. R.14 ШАБЛОНЫ ТИПА R.14.1 Шаблоны типа Шаблон типа определяет целое семейство типов или функций. описание-шаблона-типа: template < список-параметров-шаблона-типа> описание список-параметров-шаблона-типа: параметр-шаблона-типа список-параметров-шаблона-типа , параметр-шаблона-типа параметр-шаблона-типа: параметр-типа описание-параметра параметр-типа: class идентификатор Конструкция описание в описании-шаблона-типа должна содержать описание или определение функции или класса. В конструкции параметр-типа идентификатор определяется как имя-типа в области видимости описания шаблона типа. Имена шаблонов типа подчиняются обычным правилам для областей видимости и контроля доступа. Конструкция описание-шаблона-типа считается описанием. Она может присутствовать в программе только как глобальное описание. R.14.2 Шаблоны типов для классов Шаблон типа для класса определяет как будут строиться классы, подобно тому, как описание класса определяет как будут строиться объекты этого класса. Шаблон типа для класса vector можно описать следующим образом: 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. Класс можно задать с помощью конструкции имя-шаблонного-класса: имя-шаблонного-класса: имя-шаблона-типа < список-парам-шаблона-типа > список-парам-шаблона-типа: парам-шаблона-типа список-парам-шаблона-типа , парам-шаблона-типа парам-шаблона: выражение имя-типа Конструкция имя-шаблонного-класса является именем-класса ($$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. R.14.3 Эквивалентность типов Две конструкции шаблонное-имя-класса обозначают один и тот же класс, если в них совпадают имена шаблонов типа и значения указанных параметров. Например, в следующих описаниях 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; R.14.4 Шаблоны типа для функций Шаблон типа для функции определяет как будет строиться функция. Например, семейство функций 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; // ... } Все параметры-шаблона-типа, приведенные в шаблоне типа для функции, должны быть параметрами-типа. R.14.5 Описания и определения Для каждого имени шаблона типа в программе должно существовать только одно определение. Описаний может быть несколько. Определение используется для создания специальных шаблонных классов и шаблонных функций, которые будут соответствовать шаблону типа. Конструкция имя-шаблонного-класса вводит описание шаблонного класса. Вызов шаблонной функции или взятие ее адреса вводит описание шаблонной функции. Для вызова или взятия адреса шаблонной функции в языке существует особое соглашение: имя шаблонной функции используется точно так же как имя обычной функции. Описание функции с таким же именем, как у шаблонной функции, и с сопоставимыми типами параметров, вводит описание специальной шаблонной функции. Если для выполнения некоторых операций требуется определение специального шаблонного класса или специальной шаблонной функции, и если такого определения в программе нет, то оно будет создано. Определение обычной (нешаблонной) функции с типом, который точно сопоставляется с типом из описания шаблонной функции, считается определением специальной шаблонной функции. Рассмотрим пример: 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>). Другие потоки будут управляться с помощью шаблонных функций, создаваемых по шаблону типа для функций. Пока не появится описание шаблона типа для класса, никакие операции, которым требуется определенный класс, не могут быть произведены над шаблонным классом. После этого специальный шаблонный класс будет считаться определенным, причем сразу же перед первым глобальным описанием, использующим его имя. R.14.6 Функции-члены шаблонов типа Функция-член шаблонного класса считается неявной шаблонной функцией, а параметры шаблона типа для ее класса - ее шаблонными параметрами. Приведем пример, в котором описаны три шаблона типа для функции: 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[]() R.14.7 Друзья Функция-друг для шаблона типа не является неявной шаблонной функцией, например: 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>, и т.д. R.14.8 Статические члены и переменные Для каждого шаблонного класса или функции, создаваемых по шаблону типа, образуется своя копия статических переменных или членов. Рассмотрим пример: 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); } R.15 Обработка особых ситуаций R.15.1 Обработка особых ситуаций При обработке особых ситуаций в ходе выполнения программы информация и управление передаются из некоторой точки обработчику особых ситуаций. Обработчик находится в цепочке выполненных вызовов функций. Управление обработчику передается с помощью выражения-запуска, которое может быть только в проверяемом-блоке обработчика или в функции, вызванной из проверяемого-блока. проверяемый-блок: try составной-оператор список-обработчиков список-обработчиков: обработчик список-обработчиков opt обработчик: catch ( описание-особой-ситуации ) составной-оператор описание-особой-ситуации: список-спецификаций-типа описатель список-спецификаций-типа абстрактный-описатель список-спецификаций-типа ... выражение-запуска: throw выражение opt Конструкция проверяемый-блок является оператором ($$R.6), а выражение-запуска - унарным выражением типа void ($$R.5). Иногда выражение-запуска называют "точкой запуска", а про функцию, в которой встретилось выражение-запуска, говорят, что она "запускает особую ситуацию. Часть программы, которой передается управление из точки запуска называется обработчиком. R.15.2 Запуск особой ситуации При запуске особой ситуации управление передается обработчику. Запуск сопровождается передачей объект, тип которого определяет, какой обработчик должен перехватить особую ситуацию. Так, выражение 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; // передача остальных особых ситуаций другому обработчику } R.15.3 Конструкторы и деструкторы Когда управление передается из точки запуска особой ситуации обработчику, то вызываются деструкторы для всех автоматических объектов, построенных с момента входа в проверяемый-блок. Если объект не был построен полностью, то деструкторы вызываются только для полностью построенных вложенных в него объектов. Кроме того, если особая ситуация запускается в конструкторе при построении элемента автоматического массива, то уничтожаться будут только уже построенные элементы этого массива. Процесс вызова деструкторов для уничтожения автоматических объектов, построенных в ходе выполнения программы от начала проверяемого-блока до выражения-запуска, называется "раскручиванием стека". R.15.4 Обработка особой ситуации Обработчик типа 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). Особая ситуация считается обработанной после входа в тело обработчика. В этот момент завершится "раскручивание стека". R.15.5 Спецификации особых ситуаций Возникновение и перехватывание особой ситуации влияет на взаимодействие функций. Список особых ситуаций, которые прямо или косвенно может запустить данная функция, можно задать как часть ее описания. Конструкция спецификация-особой-ситуации предшествует описателю функции. спецификация-особой-ситуации: throw ( список-типов opt ) список-типов: имя-типа список-типов , имя-типа Приведем пример: void f() throw (X,Y) { // ... } Если функция попробует запустить неуказанную в списке ситуацию, управление передается функции unexpected(), см. $$R.15.8. Реализация языка не должна запрещать выражение только потому, что при его вычислении возможен запуск особой ситуации, не указанной в спецификации-особой ситуации описания функции. Обработка непредвиденных особых ситуаций происходит в динамике. Функция, в которой отсутствует спецификация-особой-ситуации, может запустить любую особую ситуацию. Функция с пустой спецификацией-особых-ситуаций (throw()) не должна запускать никаких особых ситуаций. Если функция может запустить особую ситуацию класса X, то она может запустить особую ситуацию любого класса, являющегося общим производным классом от X. Конструкция спецификация-особой-ситуации не относится к типу функции. R.15.6 Специальные функции Механизм управления особыми ситуациями использует для реакции на ошибки при самой обработке особых ситуаций функции: terminate() и unexpected(). R.15.6.1 Функция terminate() Иногда от предусмотренной обработки особых ситуаций приходится переходить к более грубым приемам, например: - когда механизм управления особыми ситуациями не смог найти обработчик для запущенной особой ситуации; - когда механизм управления особыми ситуациями столкнулся с нарушенной структурой стека; - когда деструктор, вызванный в процессе раскрутки стека при запуске особой ситуации, сам пытается завершить выполнение программы, запустив особую ситуацию. В этих случаях вызывается функция void terminate(); Она в свою очередь вызывает функцию, которая была указана как параметр при последнем обращении к set_terminate(): typedef void(*PFV)(); PFV set_terminate(PFV); Функция, которая была задана в предыдущем вызове set_terminate(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию функция terminate() вызывает abort(). Выбор с помощью terminate() такой функции, которая вместо действительного завершения программы, пытается вернуться в вызвавшую программу, является ошибкой. R.15.6.2 Функция unexpected() Если функция, имеющая спецификацию-особой-ситуации, запускает неуказанную особую ситуацию, то вызывается функция void unexpected(); Она в свою очередь вызывает функцию, которая была задана как параметр при последнем обращении к set_unexpected(): typedef void(*PFV)(); PFV set_unexpected(PFV); Функция, которая была задана в предыдущем вызове set_unexpected(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию unexpected() вызывает terminate(). Поскольку по умолчанию terminate() вызывает abort(), результатом будет непосредственное и точное обнаружение ошибки. R.15.7 Особые ситуации и правила доступа Для формального параметра операции catch действуют такие же правила доступа, как и для формального параметра функции, в которой задана операция catch. При запуске особой ситуации можно указывать такой объект, который можно копировать и уничтожать в области видимости функции, где задана операция throw. R.16 Препроцессорная обработка Реализация языка С++ включает препроцессор с возможностями макроподстановки, условной трансляции и включения указанных файлов. Для передачи заданий препроцессору служат строки, начинающиеся с символа # (перед ним могут идти пробелы и символы горизонтальной табуляции). Такие строки называются командами, и их синтаксис определяется независимо от остального языка. Команды могут находиться в любом месте программы, и их действие продолжается (независимо от правил областей видимости С++) до конца данной единицы трансляции ($$R.2). Команду препроцессора, как и любую строку, можно продолжить на следующей строке входного текста, поместив символ обратной дробной черты непосредственно перед символом конца продолжаемой строки. Препроцессор до того, как входная строка будет разбита на лексемы, удаляет символы обратной дробной черты и конца строки. Символ обратной дробной черты не должен быть последним символом входного файла. К лексемам препроцессора относятся: лексемы самого языка ($$R.2.1), имя файла, которое используется в команде #include и вообще любой символ, отличный от обобщенного пробела и несовпадающий ни с какой из лексем препроцессора. R.16.1 Фазы препроцессорной обработки По определению существует несколько фаз препроцессорной обработки. В конкретной реализации фазы могут сливаться, но результат все равно должен быть таким, как будто были выполнены все фазы. Перечислим их. При необходимости символы, зависящие от системы символы, обозначающие конец строки, заменяются на стандартный символ конца строки. Аналогичной замене подлежат все зависящие от системы символы. Определенные последовательности символов (триграфы) заменяются на эквивалентный им отдельный символ ($$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). Сливаются соседние строки литералов. Результат препроцессорной обработки подвергается синтаксическому и семантическому анализу, транслируется, а затем связывается с необходимыми библиотеками и другими программами. R.16.2 Триграфные последовательности Прежде чем начнется какая-либо иная препроцессорная обработка, каждое вхождение триграфной последовательности заменяется на один символ в соответствии с приведенной ниже таблицей. ??= # ??( [ ??/ \ ??) [ ??' ^ ??! | Например, строка ??=define arraycheck(a,b) a??(b??) ??!??! b??(a??) преобразуется в #define arraycheck(a,b) a[b] || b[a] R.16.3 Макроопределение и макроподстановка Команда вида #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.16.3.1 Операция # Если непосредственно перед параметром в строке замены идет лексема #, то при подстановке параметр и операция # будут заменены на строку литералов , содержащую имя соответствующего параметра макровызова. В символьной константе или строке литералов, входящих в параметр, перед каждым вхождением \ или " вставляется символ \. Например, если есть макроопределения #define path(logid,cmd) "/usr/" #logid "/bin/" #cmd то макровызов char* mytool=path(joe,readmail); приведет к такому результату: char* mytool="/usr/" "joe" "/bin/" "readmail"; После конкатенации соседних строк ($$R.16.1) получим: char* mytool="/usr/joe/bin/readmail"; R.16.3.2 Операция ## Если в строке замены между двумя лексемами, одна из которых представляет параметр макроопределения, появляется операция ##, то сама операция ## и окружающие ее обобщенные пробелы удаляются. Таким образом, результат операции ## состоит в конкатенации. Пусть есть макроопределение, #define inherit(basenum) public Pubbase ## basenum, \ private Privbase ## basenum тогда макровызов class D : inherit(1) { }; приведет к такому результату: class D : public Pubbase1, Privbase1 { }; Макроопределение, которое в строке замены соседствует с ##, не подлежит подстановке, однако, результат конкатенации может использоваться для подстановки. Приведем пример. Пусть есть определения: #define concat(a) a ## ball #define base B #define baseball sport Тогда макровызов concat(base) даст в результате sport а вовсе не Bball R.16.3.3 Повторный просмотр и дальнейшие подстановки После того, как в строке замены произошла подстановка всех параметров макровызова, получившаяся строка просматривается повторно для обнаружения дополнительных макроопределений. Если в процессе повторных просмотров строки замены найдено имя макроопределения, то подстановка все же не происходит. Рекурсивную подстановку нельзя выполнить как команду препроцессора, хотя она кажется для него естественной командой. R.16.3.4 Область видимости макроимен и конструкция #undef После появления макроопределения идентификатор из него считается определенным и остается в текущей области видимости (независимо от правил областей видимости в С++) до конца единицы трансляции или пока его определение не будет отменено с помощью команды #undef. Команда #undef имеет вид: #undef идентификатор Она заставляет препроцессор "забыть" макроопределение с этим идентификатором. Если указанный идентификатор не является определенным в данный момент макроименем, то команда #undef игнорируется. R.16.4 Включение файлов Управляющая строка вида: #include <имяфайла> приводит к замене данной строки на содержимое файла с указанным именем. Поиск указанного файла проходит в определенной последовательности частей архива системы и определяется реализацией. Аналогично, управляющая строка вида: #include "имяфайла" приводит к замене данной строки на содержимое файла с указанным именем. Поиск этого файла начинается в особых (системных) частях архива, указанных в начале последовательности поиска. Если там он не найден, то поиск файла идет по всей последовательности, как если бы управляющая строка имела вид: #include <имяфайла> В имени файла, ограниченном символами < и > нельзя использовать символы конца строки или >. Если в таком имени появится один из символов ', \, или ", а также последовательность символов /* или //, то результат считается неопределенным. В имени файла, ограниченном парой символов " нельзя использовать символы конца строки или ", хотя символ > допустим. Если в таком имени появится символ ' или \ или последовательность /* или //, то результат считается неопределенным. Если команда #include строка-лексем имеет вид, соответствующий ни первой, ни второй управляющей строке, то лексемы препроцессора, заданные в этой команде обрабатываются как обычный текст. В результате должна получиться команда, вид которой соответствует одному из приведенных. Она и будет выполнена как положено. Команда #include может быть в файле, который сам появился в результате выполнения другой команды #include. Реализация может накладывать ограничение на глубину вложенности команды #include во входных файлах программы, которые приходится читать для выполнения первоначальной команды #include в одном из входных файлов. R.16.5 Условная трансляция С помощью препроцессора можно организовать условную трансляцию программы. Синтаксически это задается следующим образом: условное: часть-if части-elif opt часть-else opt строка-endif часть-if: строка-if текст строка-if: # if выражение-константа # ifdef идентификатор # ifndef идентификатор части-elif: строка-elif текст части-elif строка-elif текст строка-elif: # elif выражение-константа часть-else: строка-else текст строка-else: # else строка-endif: # endif Константные выражения в #if и #elif (если эти части есть) вычисляются в порядке их задания в тексте до тех пор, пока одно из них не окажется отличным от нуля. Операторы С++, следующие за строкой, в которой выражение оказалось равным нулю, не транслируются. Команды препроцессора, идущие за этой строкой игнорируются. После того, как найдена команда с ненулевым значением выражения, текст всех последующих частей #elif и #else (т.е. операторы С++ и команды препроцессора) игнорируется. Текст, относящийся к первой команде с ненулевым значением выражения подлежит обычной препроцессорной обработке и трансляции. Если значения всех выражений, указанных в #if и #elif, оказались равными нулю, тогда обычной обработке подлежит текст, относящийся к #else. В выражении-константе, которое встретилось в #if или #elif можно использовать унарную операцию defined, причем в двух вариантах: defined идентификатор или defined (идентификатор) Если эта операция применяется к идентификатору, который был определен с помощью команды #define, и если это определение не было отменено командой #undef, то результат равен 1, иначе результат равен 0. Сам идентификатор defined нельзя переопределить, нельзя и отменить его определение. После применения операций defined происходит раскрытие всех всех макроопределений, имеющихся в константном выражении см. $$R.16.3. В результате должно получиться целочисленное выражение-константа, отличающееся от определения в $$R.5.19 тем, что типы int и unsigned int рассматриваются как long и unsigned long соответственно, а кроме того в этом выражении не должно быть операций приведения, sizeof или элемента перечисления. Управляющая строка #ifdef идентификатор эквивалентна строке #if defined идентификатор а управляющая строка #ifndef идентификатор эквивалентна строке #if !defined идентификатор Конструкции, задающие условную трансляцию, могут быть вложенными, но реализация может накладывать ограничение на глубину вложенности этих конструкций. R.16.6 Управление строками Для удобства написания программ, порождающих текст на С++, введена управляющая строка вида: #line константа "имяфайла" opt Она задает значение предопределенному макроимени __LINE__ ($$R.16.10), которое используется в диагностических сообщениях или при символической отладке; а именно: номер следующей строки входного текста считается равным заданной константе, которая должна быть десятичным целым числом. Если задано "имяфайла", то значение макроимени __FILE__ ($$R.16.10) становится равным имени указанного файла. Если оно не задано, __FILE__ не меняет своего значения. Макроопределения в этой управляющей строке раскрываются до выполнения самой команды. R.16.7 Команда error Строка вида: #error строка-лексем заставляет реализацию выдать диагностическое сообщение, состоящее из заданной последовательности лексем препроцессора. R.16.8 Команда pragma Строка вида: #pragma строка-лексем заставляет реализацию вести себя некоторым определенным образом при условии что реализация "понимает" эту строку. Любая нераспознанная строка #pragma игнорируется. R.16.9 Пустая директива Команда препроцессора вида # не оказывает никакого действия. R.16.10 Предопределенные макроимена В процессе трансляции определенную информацию содержат следующие предопределенные макроимена. __LINE__ десятичная константа, содержащая номер текущей строки текста программы на С++. __FILE__ строка литералов, представляющая имя транслируемого входного файла. __DATE__ строка литералов, представляющая дату трансляции в виде "Mmm dd yyyy" или "Mmm d yyyy", если число меньше 10, (здесь Mmm задает месяц, dd - день, а yyyy - год). __TIME__ строка литералов, представляющая время трансляции в виде "hh:mm:ss", (здесь hh задает часы, mm - минуты, а ss - секунды). Кроме того, считается определенным при трансляции программы на С++ макроимя __cplusplus. Перечисленные макроимена нельзя как переопределять, так и отменять их определения. Макроимена __LINE__ и __FILE__ можно определить с помощью команды #line ($$R.16.6). Определено ли макроимя __STDC, и если да, то каково его значение, зависит от реализации. R.17 Приложение A: Сводка грамматики Это приложение не относится к справочному руководству языка и не является определением конструкций C++. Она только должно служить более полному пониманию С++. Нельзя рассматривать его как точное определение языка, так как описанная здесь грамматика допускает произвольное множество конструкций, каждая из которых законна для С++. Чтобы различать выражения и описания, следует применять правила разрешения неопределенности ($$r.6.8, $$R.7.1, $$R.10.1.1). Далее, для отсеивания синтаксически правильных, но бессмысленных, конструкций следует применять правила контроля доступа, разрешения неопределенности и контроля типа. R.17.1 Служебные слова В описаниях: typedef ($$R.7.1.3), класса ($$R.9), перечисления ($$R.7.2), шаблона типа - ($$R.14) введены новые, зависящие от контекста, служебные слова, а именно: имя-класса: идентификатор имя-перечисления: идентификатор имя-typedef: идентификатор Отметим, что имя-typedef, обозначающее класс, является в то же время конструкцией имя-класса ($$R.9.1). R.17.2 Выражения выражение: выражение-присваивания выражение, выражение-присваивания выражение-присваивания: выражение-условия унарное-выражение операция-присваивания выражение-присваивания операция-присваивания: один из = *= /= %= += -= >>= <<= &= ^= |= выражение-условия: логическое-выражение-ИЛИ логическое-выражение-ИЛИ ? выражение : выражение-условия логическое-выражение-ИЛИ: логическое-выражение-И логическое-выражение-ИЛИ || логическое-выражение-И логическое-выражение-И: выражение-ИЛИ логическое-выражение-И && выражение-ИЛИ выражение-ИЛИ: выражение-исключающего-ИЛИ выражение-ИЛИ | выражение-исключающего-ИЛИ выражение-исключающего-ИЛИ: выражение-И выражение-исключающего-ИЛИ ^ выражение-И выражение-И: выражение-равенства выражение-И & выражение-равенства выражение-равенства: выражение-отношения выражение-равенства == выражение-отношения выражение-равенства != выражение-отношения выражение-отношения: сдвиговое-выражение выражение-отношения < сдвиговое-выражение выражение-отношения > сдвиговое-выражение выражение-отношения <= сдвиговое-выражение выражение-отношения >= сдвиговое-выражение сдвиговое-выражение: аддитивное-выражение сдвиговое-выражение << аддитивное выражение сдвиговое-выражение >> аддитивное выражение аддитивное-выражение: мультипликативное-выражение аддитивное выражение + мультипликативное-выражение аддитивное-выражение - мультипликативное-выражение мультипликативное-выражение: выражение-pm мультипликативное-выражение * выражение-pm мультипликативное-выражение / выражение-pm мультипликативное-выражение % выражение-pm выражение-pm: выражение-приведения выражение-pm .* выражение-приведения выражение-pm ->* выражение-приведения выражение-приведения: унарное-выражение ( имя-типа ) выражение-приведения унарное-выражение: постфиксное-выражение ++ унарное выражение -- унарное выражение унарная-операция выражение-приведения sizeof унарная-операция sizeof ( имя-типа ) выражение-размещения выражение-освобождения унарная-операция: один из * & + - ! ~ выражение-размещения: ::opt new параметры-new opt имя-типа-new инициализатор-new ::opt new параметры-new opt ( имя-типа ) инициализатор-new параметры-new: ( список-выражений ) имя-типа-new: список-спецификаций-типа описатель-new opt описатель-new: * список-спецификаций-cv opt описатель-new opt имя-класса :: список-спецификаций-cv opt описатель-new opt описатель-new opt [ выражение ] инициализатор-new: ( список-инициализаторов opt ) выражение-освобождения: ::opt delete выражение-приведения ::opt delete [] выражение-приведения постфиксное-выражение: первичное-выражение постфиксное-выражение [ выражение ] постфиксное-выражение ( список-выражений opt ) имя-простого-типа ( список-выражений opt ) постфиксное-выражение . имя постфиксное-выражение -> имя постфиксное-выражение ++ постфиксное-выражение -- список-выражений: выражение-присваивания список-выражений , выражение-присваивания первичное-выражение: литерал this :: идентификатор :: имя-функции-операции :: уточненное-имя ( выражение ) имя имя: идентификатор имя-функции-операции имя-функции-преобразования ~имя-класса уточненное-имя уточненное-имя: уточняющее-имя-класса :: имя литерал: целая константа символьная константа константа с плавающей точкой строка литералов R.17.3 Описания описания: спецификации-описания opt список-описателей opt ; описание-asm определение-функции спецификация-связи спецификация-описания: спецификация-класса-памяти спецификация-типа спецификация-fct спецификация-шаблона-типа friend typedef спецификации-описания: спецификации-описания opt спецификация-описания спецификация-класса-памяти: auto register static extern спецификация-fct: inline virtual спецификация-типа: имя-простого-типа спецификация-класса спецификация-перечисления спецификация-сложного-типа :: имя-класса const volatile имя-простого-типа: полное-имя-класса уточненное-имя-типа char short int long signed unsigned float double void спецификация-сложного-типа: служебное-слово-класса имя-класса служебное-слово-класса идентификатор служебное-слово-класса: class struct union уточненное-имя-типа: имя-typedef имя-класса :: уточненное-имя-типа полное-имя-класса: уточненное-имя-класса :: уточненное-имя-класса уточненное-имя-класса: имя-класса имя-класса :: уточненное-имя-класса имя-перечисления: идентификатор спецификация-перечисления: enum идентификатор opt { список-перечисления } список-перечисления: элемент-перечисления список-перечисления , элемент-перечисления элемент-перечисления: идентификатор идентификатор = выражение-константа спецификация-связи: extern строка-литерал { список-описаний opt } extern строка-литерал описание список-описаний: описание список-описаний описание описание-asm: asm ( строка-литерал) ; R.17.4 Описатели список-описаний: описатель-с-инициализатором список-описаний , описатель-с-инициализатором описатель-с-инициализатором: описатель инициализатор opt описатель: имя-в-описателе операция-ptr описатель описатель (список-описаний-параметров) список-спецификаций-cv opt описатель [ выражение-константа opt] ( описатель ) операция-ptr: * список-спецификаций-cv opt & список-спецификаций-cv opt полное-имя-класса :: * список-спецификаций-cv opt список-спецификаций-cv: const volatile имя-в-описателе: имя имя-класса ~имя-класса имя-typedef уточненное-имя-типа имя-типа: список-спецификаций-типа абстрактный-описатель opt список-спецификаций-типа: спецификация-типа список-спецификаций-типа абстрактный-описатель: операция-ptr абстрактный-описатель opt абстрактный-описатель opt ( список-описаний-параметров ) список-спецификаций_cv opt абстрактный-описатель opt [ выражение-константа opt ] ( абстрактный-описатель ) список-описаний-параметров: список-описаний-парам opt ... opt список-описаний-парам , ... список-описаний-парам: описание-параметра список-описаний-парам , описание-параметра описание-параметра: спецификации-описания описатель спецификации-описания описатель = выражение спецификации-описания абстрактный-описатель opt спецификации-описания абстрактный-описатель opt = выражение определение-функции: спецификации-описания opt описатель инициализатор-ctor тело-функции тело-функции: составной-оператор инициализатор: = выражение-присваивания = { список-инициализаторов , opt } ( список-выражений ) список-инициализаторов: выражение-присваивания список-инициализаторов , выражение-присваивания { список-инициализаторов , opt } R.17.5 Описания класса спецификация-класса: заголовок-класса { список-членов opt } заголовок-класса: служебное-слово-класса идентификатор opt спец-базовых opt служебное-слово-класса имя-класса спец-базовых opt служебное-слово-класса: class struct union список-членов: описание-члена список-членов opt спецификация-доступа : список-членов opt описание-члена: спецификации-описания opt список-описателей-членов opt ; определение-функции ; opt уточненное-имя ; список-описателей-членов: описатель-члена список-описателей-членов , описатель-члена описатель-члена: описатель спецификация-чистой opt идентификатор opt : выражение-константа спецификация-чистой: = 0 список-базовых: спецификация-базовых список-базовых , спецификация-базовых спецификация-базовых: полное-имя-класса virtual спецификация-доступа opt полное-имя-класса спецификация-доступа virtual opt полное-имя-класса спецификация-доступа: private protected public имя-функции-преобразования: operator имя-типа-преобразования имя-типа-преобразования: список-спецификаций-типа операция-ptr opt инициализатор-ctor: : список-инициализаторов-членов список-инициализаторов-членов: инициализатор-члена инициализатор-члена , список-инициализаторов-члена инициализатор-члена: полное-имя-класса ( список-выражений opt ) идентификатор имя-функции-оператор: operator операция операция: один из new delete + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] R.17.6 Операторы оператор: помеченный-оператор оператор-выражение составной-оператор выбирающий-оператор оператор-цикла оператор-перехода оператор-описания помеченный-оператор: идентификатор : оператор case выражение-константа : оператор default : оператор оператор-выражение: выражение opt ; составной-оператор: { список-операторов opt } список-операторов: оператор список-операторов оператор выбирающий-оператор: if ( выражение ) оператор if ( выражение ) оператор else оператор switch ( выражение ) оператор оператор-цикла: while ( выражение ) оператор do оператор while (выражение) for ( оператор-иниц выражение opt ; выражение opt ) оператор оператор-иниц: оператор-выражение оператор-описание оператор-перехода: break ; continue ; return выражение opt ; goto идентификатор ; оператор-описания: описание R.17.7 Препроцессор #define идентификатор строка-лексем #define идентификатор ( идентификатор , ... , идентификатор ) строка-лексем #include "имяфайла" #include <имяфайла> #line константа "имяфайла" opt #undef идентификатор условное: часть-if части-elif opt часть-else opt строка-endif часть-if: строка-if текст строка-if: # if выражение-константа # ifdef идентификатор # ifndef идентификатор части-elif: строка-elif текст части-elif строка-elif текст строка-elif: # elif выражение-константа часть-else: строка-else текст строка-else: # else строка-endif: # endif R.17.8 Шаблоны типа описание-шаблона-типа: template < список-параметров-шаблона-типа> описание список-параметров-шаблона-типа: параметр-шаблона-типа список-параметров-шаблона-типа , параметр-шаблона-типа параметр-шаблона-типа: параметр-типа описание-параметра параметр-типа: class идентификатор имя-шаблонного-класса: имя-шаблона-типа < список-парам-шаблона-типа > список-парам-шаблона-типа: парам-шаблона-типа список-парам-шаблона-типа , парам-шаблона-типа парам-шаблона: выражение имя-типа R.17.9 Обработка особых ситуаций проверяемый-блок: try составной-оператор список-обработчиков список-обработчиков: обработчик список-обработчиков opt обработчик: catch ( описание-особой-ситуации ) составной-оператор описание-особой-ситуации: список-спецификаций-типа описатель список-спецификаций-типа абстрактный-описатель список-спецификаций-типа ... выражение-запуска: throw выражение opt спецификация-особой-ситуации: throw ( список-типа opt ) список-типа: имя-типа список-типа , имя-типа R.18 Приложение B: Совместимость Это приложение не относится к справочному руководству С++ и не является определением конструкций языка. Язык С++ основывается на С (описание в книге Кернигана и Ритчи, 78 г., дальше K&R) и включает большинство изменений, предложенных в ANSI стандарте для С. При конвертировании программ на языках С++, K&R C и ANSI C могут возникнуть трудности в связи с различным вычислением в них выражений. Транслятор должен распознавать все различия между С++ и ANSI C. Программы на С++ и ANSI C должны иметь одинаковый смысл за исключением трех следующих случаев: В языке С выражение sizeof('a') равно sizeof(int), а в С++ оно равно sizeof(char). Если есть описание enum e { A }; то sizeof(A) равно в С sizeof(int), тогда как в С++ оно равно sizeof(e) и не обязано быть равно sizeof(int). Имя структуры, описанной во внутреннем блоке, может скрывать имя объекта, функции, элемента перечисления или типа из внешнего блока. Приведем пример: int x[99]; void f() { struct x { int a; }; sizeof(x); /* для C это размер массива */ /* а для C++ размер структуры */ } R.18.1 Расширения В этом разделе перечисляются основные расширения языка С, введенные в С++. R.18.1.1 Возможности С++, введенные в 1985 г. Здесь перечисляются возможности, добавленные к С, версией языка С++ 1985 г. Можно указывать типы формальных параметров функции ($$R.8.2.5), и они будут проверяться ($$R.5.2.2). Будет происходить преобразование типа ($$R.5.2.2). Это есть и в ANSI C. В выражениях со значениями типа float вычисления могут проходить с обычной точностью ($$R.3.6.1 и $$R.4.3). Это есть и в ANSI C. Можно перегружать имена функций; $$R.13. Можно перегружать операции; $$R.13.4 Возможна реализация вызова функций подстановкой; $$R.7.1.2. Можно описывать объекты, представляющие данные, со спецификацией const; $$R.7.1.6. Это есть и в ANSI C. Можно описывать типа ссылки; $$R.8.2.2 и $$R.8.4.3. Возможно управление свободной памятью с помощью операций new и delete; $$R.5.3.3 и $$R.5.3.4. Введены классы, которые позволяют: скрывать информацию ($$R.11), проводить инициализацию ($$R.12.1), осуществлять пользовательские преобразования типа ($$R.12.3) и работать с динамическими типами с помощью виртуальных функций ($$R.10.2). Имя класса или перечисления считается именем типа; $$R.9. Указатель на любой объект c типом, не являющимся const или volatile, можно присвоить указателю типа void*. Это есть и в ANSI C. Указатель на функцию можно присваивать указателю типа void*; $$R.4.6. Описание внутри блока считается оператором; $$R.6.7. Можно описывать безымянные объединения; $$R.9.5. R.18.1.2 Возможности, добавленные в С++ после 1985 г. Здесь перечисляются основные расширения С++ после 1985 г.: Класс может иметь более одного прямого базового класса (множественное наследование); $$R.10.1. Члены класса могут быть защищенными; $$R.11. Операции new и delete можно описывать в классе и перегружать; $$r.5.3.3, $$R.5.3.4, $$R.12.5. Это позволило определенный способ управления памятью для класса с помощью "присваивания указателю this" отнести в раздел анахронизмов; $$R.18.3.3. Можно явно уничтожать объекты; $$R.12.4. Присваивания и инициализация для класса определены как присваивание и инициализация по членам; $$R.12.8. Служебное слово overload стало излишним и отнесено к разделу анахронизмов; $$R.18.3. Произвольные выражения разрешены в качестве инициализаторов статических объектов; $$R.8.4. Объекты, представляющие данные, могут быть volatile; $$R.7.1.6. Также и в ANSI C. Допустимы инициализаторы для статических членов класса; $$R.9.4. Функции-члены могут быть статическими; $$R.9.4. Функции-члены могут быть const или volatile; $$R.9.3.1. Можно явно указать связывание с подпрограммами на других языках; $$R.7.4. Можно перегружать операции ->, ->* и ` ; $$R.13.4. Классы могут быть абстрактными; $$R.