18 Exception handling [except]

Обработка исключений обеспечивает способ передачи управления и информации от точки выполнения потока обработчику исключений, связанному с точкой, ранее переданной выполнением. Обработчик будет вызываться только путем выдачи исключения в коде, выполняемом в блоке try обработчика или в функциях, вызываемых из блока try обработчика.

try-block:
	try compound-statement handler-seq

function-try-block:
	try ctor-initializeropt compound-statement handler-seq
handler-seq:
	handler handler-seqopt

handler:
	catch ( exception-declaration ) compound-statement
exception-declaration:
	attribute-specifier-seqopt type-specifier-seq declarator
	attribute-specifier-seqopt type-specifier-seq abstract-declaratoropt
	...

Необязательный параметр attribute-specifier-seqв exception-declaration объекте принадлежит параметру предложения catch ([except.handle]).

А try-block- это statement. [ Note: В этом разделе «блок попыток» означает оба try-blockи function-try-block. ] end note

goto Или switch заявление не может быть использовано для передачи управления в попытке блок или в обработчик. [Example:

void f() {
  goto l1;          // ill-formed
  goto l2;          // ill-formed
  try {
    goto l1;        // OK
    goto l2;        // ill-formed
    l1: ;
  } catch (...) {
    l2: ;
    goto l1;        // ill-formed
    goto l2;        // OK
  }
}

end example] , , Или заявление может быть использовано для контроля передачи из блока Ьги или обработчика. Когда это происходит, каждая переменная, объявленная в блоке try, будет уничтожена в контексте, который непосредственно содержит ее объявление. [gotobreakreturncontinueExample:

lab:  try {
  T1 t1;
  try {
    T2 t2;
    if (condition)
      goto lab;
    } catch(...) { /* handler 2 */ }
  } catch(...) { /* handler 1 */ }

Здесь выполнение goto lab; сначала уничтожит t2, а затем t1, если condition не объявляет переменную. Любое исключение, возникшее при уничтожении t2 , приведет к выполнению handler 2; любое исключение, возникшее при уничтожении t1 , приведет к выполнению handler 1. ]end example

A function-try-block связывает a handler-seq с элементом ctor-initializer, если он присутствует, и с compound-statement. Исключение, созданное во время выполнения compound-statement или, для конструкторов и деструкторов, во время инициализации или уничтожения, соответственно, подобъектов класса, передает управление обработчику таким function-try-block же образом, как исключение, созданное во время выполнения элемента try-block управления передачей. другим обработчикам. [Example:

int f(int);
class C {
  int i;
  double d;
public:
  C(int, double);
};

C::C(int ii, double id)
try : i(f(ii)), d(id) {
    // constructor statements
} catch (...) {
    // handles exceptions thrown from the ctor-initializer and from the constructor statements
}

end example]

В этом разделе «до» и «после» относятся к “sequenced before” отношению.

18.1 Throwing an exception [except.throw]

Создание исключения передает управление обработчику. [ Note: Исключение может быть выброшено из одного из следующих контекстов: throw-expressions, allocation functions, dynamic_­cast, typeid, new-expressions, и стандартные библиотечные функции ([structure.specifications]). ] Объект передается, и тип этого объекта определяет, какие обработчики могут его перехватить. [end noteExample:

throw "Help!";

может быть захвачено handler из const char* типа:

try {
    // ...
} catch(const char* p) {
    // handle character string exceptions here
}

а также

class Overflow {
public:
    Overflow(char,double,double);
};

void f(double x) {
    throw Overflow('+',x,3.45e107);
}

может быть перехвачен обработчиком исключений типа Overflow:

try {
    f(1.2);
} catch(Overflow& oo) {
    // handle exceptions of type Overflow here
}

end example]

Когда генерируется исключение, управление передается ближайшему обработчику с подходящим типом ([except.handle]); «Ближайший» означает обработчик, для которого ключевое слово compound-statementили ctor-initializer следующее за tryключевым словом было самым последним введено потоком управления и еще не завершено.

Создание исключения инициализирует копирование-инициализацию ([dcl.init], [class.copy]) временного объекта, называемого exception object. Значение lvalue, обозначающее временное, используется для инициализации переменной, объявленной в match handler([except.handle]). Если тип объекта исключения будет неполным типом или указатель на неполный тип, отличный cv void от программы, сформирован неправильно.

Память для объекта исключения распределяется неопределенным образом, за исключением случаев, указанных в [basic.stc.dynamic.allocation]. Если обработчик завершает работу путем повторной генерации, управление передается другому обработчику для того же объекта исключения. Точки потенциального разрушения объекта исключения:

  • когда активный обработчик исключения завершает работу любым способом, кроме повторной генерации, сразу после уничтожения объекта (если есть), объявленного exception-declarationв обработчике;

  • когда объект типа, std​::​exception_­ptr который ссылается на объект исключения, уничтожается до того, как деструктор std​::​exception_­ptr возвращает.

Среди всех точек потенциального разрушения для объекта исключения есть неуказанная последняя, ​​в которой уничтожается объект исключения. Все остальные пункты, happen before которые последний. [ Note: Никакая другая синхронизация потоков не подразумевается при обработке исключений. ] Затем реализация может освободить память для объекта исключения; любое такое освобождение выполняется неуказанным образом. [ Выброшенное исключение не распространяется на другие потоки, если оно не будет поймано, сохранено и повторно создано с использованием соответствующих библиотечных функций; видеть и . ]end noteNote: [propagation] [futures] end note

Когда брошенный объект является объектом класса, конструктор, выбранный для инициализации копирования, а также конструктор, выбранный для инициализации копирования, учитывая, что брошенный объект является lvalue, не должны быть удалены и доступны, даже если операция копирования / перемещения есть elided. Деструктор есть potentially invoked.

Исключение считается пойманным, когда его обработчик становится active. [ Note: Исключение может иметь активные обработчики и по-прежнему считаться неперехваченным, если оно генерируется повторно. ]end note

Если механизм обработки исключений, обрабатывающий объект, uncaught exception напрямую вызывает функцию, которая завершается через исключение, std​::​terminate вызывается. [Example:

struct C {
  C() { }
  C(const C&) {
    if (std::uncaught_exceptions()) {
      throw 0;      // throw during copy to handler's exception-declaration object ([except.handle])
    }
  }
};

int main() {
  try {
    throw C();      // calls std​::​terminate() if construction of the handler's
                    // exception-declaration object is not elided
  } catch(C) { }
}

end example] [ Note: Следовательно, деструкторы обычно должны перехватывать исключения и не позволять им распространяться. ]end note

18.2 Constructors and destructors [except.ctor]

Когда управление переходит от точки, в которой возникло исключение, к обработчику, деструкторы вызываются процессом, указанным в этом разделе stack unwinding.

Деструктор вызывается для каждого автоматического объекта типа класса, созданного, но еще не уничтоженного, так как был введен блок try. Если во время уничтожения временных или локальных переменных для a возникает исключение, return statementтакже вызывается деструктор для возвращенного объекта (если есть). Объекты уничтожаются в порядке, обратном завершению их строительства. [Example:

struct A { };

struct Y { ~Y() noexcept(false) { throw 0; } };

A f() {
  try {
    A a;
    Y y;
    A b;
    return {};      // #1
  } catch (...) {
  }
  return {};        // #2
}

В №1 создается возвращаемый объект типа A . Затем локальная переменная b уничтожается ([stmt.jump]). Затем локальная переменная y уничтожается, вызывая раскручивание стека, что приводит к уничтожению возвращенного объекта с последующим уничтожением локальной переменной a. Наконец, возвращенный объект снова создается в # 2. ]end example

Если инициализация или уничтожение объекта, отличного от делегирующего конструктора, завершается исключением, деструктор вызывается для каждого из прямых подобъектов объекта и, для полного объекта, подобъектов виртуального базового класса, инициализация которых завершена ([dcl.init]) и чья деструктор еще не начал выполнение, за исключением того, что в случае уничтожения варианты членов объединенного класса не уничтожаются. Подобъекты уничтожаются в порядке, обратном завершению их строительства. Такое уничтожение выполняется до входа в обработчик function-try-blockконструктора или деструктора, если таковой имеется.

Если compound-statement компонент function-body делегирующего конструктора для объекта завершается через исключение, вызывается деструктор объекта. Такое уничтожение выполняется до входа в обработчик function-try-blockконструктора делегирования для этого объекта, если таковой имеется.

[ Note: Если объект был выделен a new-expression, то соответствие deallocation function, если таковое имеется, вызывается для освобождения памяти, занимаемой объектом. ]end note

18.3 Handling an exception [except.handle]

В exception-declaration a handler описываются типы исключений, которые могут привести handler к его вводу. Не должен обозначать неполный тип, тип абстрактного класса или ссылочный тип rvalue. Не будет обозначать указатель или ссылку на неполный тип, кроме , , или .exception-declarationexception-declarationvoid*const void*volatile void*const volatile void*

Обработчик типа «массив T» или типа функции T настраивается на тип «указатель на T».

A handler соответствует объекту исключения типа, E если

  • Имеет handlerтип cv T или cv T& и E и T относятся к тому же типу (без учета верхнего уровня cv-qualifiers), или

  • handlerимеет типа cv T или cv T& и T это однозначный общественные базовый класс E, или

  • handlerимеет тип , cv T или , const T& где T это указатель или указатель на тип члена и E представляет собой указатель или указатель на тип элемента , который может быть преобразован в T один или более из

  • handlerимеет тип , cv T или , const T& где T это указатель или указатель на тип члена и E является std​::​nullptr_­t.

[ Note: A throw-expression , операнд которого является целочисленным литералом со значением ноль, не соответствует обработчику указателя или указателя на тип члена. Обработчик ссылки на массив или тип функции никогда не соответствует какому-либо объекту исключения ([expr.throw]). ]end note

[Example:

class Matherr { /* ... */ virtual void 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. ]end example

Обработчики для блока try пробуются в порядке появления. [ Note: Это позволяет писать обработчики, которые никогда не могут быть выполнены, например, путем размещения обработчика для окончательного производного класса после обработчика для соответствующего однозначного общедоступного базового класса. ]end note

A ... в exception-declaration функциях обработчика аналогично ... объявлению параметра функции; он определяет совпадение для любого исключения. Если присутствует, ... обработчик должен быть последним обработчиком своего блока try.

Если среди обработчиков для блока try не найдено совпадений, поиск соответствующего обработчика продолжается в динамически окружающем блоке try того же потока.

Обработчик считается active завершенным после завершения инициализации параметра (если есть) в предложении catch. [ Note: В этот момент стек будет размотан. ] Кроме того, неявный обработчик считается активным, когда он вводится из-за выброса. Обработчик больше не считается активным после выхода из предложения catch.end notestd​::​terminate()

Исключение с последним активированным обработчиком, который все еще активен, называется currently handled exception.

Если соответствующий обработчик не найден,std​::​terminate() вызывается функция ; независимо от того, разматывается ли стек до того, как этот вызов std​::​terminate() определяется реализацией ([except.terminate]).

Обращение к любому нестатическому члену или базовому классу объекта в обработчике function-try-block конструктора или деструктора для этого объекта приводит к неопределенному поведению.

Область действия и время жизни параметров функции или конструктора распространяется на обработчики файла function-try-block.

Исключения , в деструкторах объектов со статической продолжительностью хранения или в конструкторах пространства имен область видимости объектов со статической продолжительностью хранения не пойманы function-try-block на main function. Исключения, возникающие в деструкторах объектов с длительностью хранения потока или в конструкторах объектов области пространства имен с длительностью хранения потока, не перехватываются a function-try-block в начальной функции потока.

Если оператор return появляется в обработчике function-try-block конструктора, программа имеет неправильный формат.

Текущее обработанное исключение генерируется повторно, если элемент управления достигает конца обработчика function-try-block конструктора или деструктора. В противном случае, истечение конца compound-statement a handler из a function-try-block эквивалентно истечению конца compound-statement этой функции (см. [stmt.return]).

Переменная, объявленная объектом exception-declarationтипа cv T или cv T&, инициализируется из объекта исключения типа Eследующим образом:

  • если T является базовым классом E, переменная происходит copy-initialized из соответствующего подобъекта базового класса объекта исключения;

  • в противном случае переменная берется copy-initialized из объекта исключения.

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

Когда обработчик объявляет объект, любые изменения этого объекта не влияют на объект исключения. Когда обработчик объявляет ссылку на объект, любые изменения в указанном объекте являются изменениями объекта исключения и вступят в силу, если этот объект будет повторно создан.

18.4 Exception specifications [except.spec]

Предикат, указывающий, не может ли функция выйти из-за исключения, называется предикатом exception specification функции. Если предикат ложен, функция имеет potentially-throwing exception specification, в противном случае - non-throwing exception specification. Спецификация исключения определяется неявно или явно с помощью noexcept-specifier суффикса a function declarator.

noexcept-specifier:
	noexcept ( constant-expression )
	noexcept
	throw ( )

В a noexcept-specifier, constant-expressionесли указано , должно быть a contextually converted constant expression of type bool; это постоянное выражение является спецификацией исключения для типа функции, в которой noexcept-specifierпоявляется. Следующий ( токен noexcept является частью noexcept-specifierи не является началом initializer. noexcept-specifier noexcept Без constant-expression эквивалентно noexcept-specifier noexcept(true). noexcept-specifier throw() Это deprecatedи эквивалентно noexcept-specifier noexcept(true).

Если объявление функции не имеет noexcept-specifier, то объявление имеет спецификацию потенциально генерирующего исключения, если только оно не является деструктором или функцией освобождения или не используется по умолчанию в своем первом объявлении, и в этих случаях спецификация исключения такая, как указано ниже, и никакое другое объявление для этой функции должен быть noexcept-specifier. В explicit instantiation a noexcept-specifierможет быть указано, но не обязательно. Если a noexcept-specifierуказан в явной директиве создания экземпляра, спецификация исключения должна быть такой же, как спецификация исключения всех других объявлений этой функции. Диагностика требуется только в том случае, если спецификации исключений не совпадают в одной единице трансляции.

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

struct B {
  virtual void f() noexcept;
  virtual void g();
  virtual void h() noexcept = delete;
};

struct D: B {
  void f();                     // ill-formed
  void g() noexcept;            // OK
  void h() = delete;            // OK
};

Объявление D​::​f неправильно сформировано, потому что в нем есть спецификация потенциально вызывающего исключения, тогда как в немB​::​f есть спецификация не вызывающего исключения. ]end example

Каждый раз, когда генерируется исключение и поиск handler ([except.handle]) встречает самый внешний блок функции со спецификацией исключения, не вызывающей выброса, функция std​::​terminate() вызывается. [ Note: Реализация не должна отклонять выражение только потому, что при выполнении оно вызывает или может вызвать исключение из функции со спецификацией не вызывающего исключения. ] [end noteExample:

extern void f();                // potentially-throwing

void g() noexcept {
  f();                          // valid, even if f throws
  throw 42;                     // valid, effectively a call to std​::​terminate
}

Вызов f правильно сформирован, хотя при его вызове f может возникнуть исключение. ]end example

Выражение e - это potentially-throwing если

  • e это function call чей postfix-expression имеет тип функции или тип указатель на функцию, с потенциально бросанием спецификации исключений, или

  • e неявно вызывает функцию (например, перегруженный оператор, функцию распределения в a new-expression, конструктор для аргумента функции или деструктор, если e это a full-expression), которая потенциально выбрасывает, или

  • e это throw-expression, или

  • e это dynamic_­cast выражение, которое приводится к ссылочному типу и требует проверки во время выполнения, или

  • e - это typeid выражение, применяемое к встроенному унарному * оператору (возможно заключенному в скобки), применяемому к указателю на тип полиморфного класса ([expr.typeid]), или

  • любой из immediate subexpressions из e потенциально метания.

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

  • конструктор, выбранный разрешением перегрузки в неявном определении конструктора для класса, X чтобы инициализировать потенциально созданный подобъект, или

  • подвыражение такой инициализации, такое как выражение аргумента по умолчанию, или,

  • для конструктора по умолчанию - инициализатор члена по умолчанию.

[ Note: Даже несмотря на то, что деструкторы для полностью сконструированных подобъектов вызываются при возникновении исключения во время выполнения конструктора ([except.ctor]), их спецификации исключений не вносят вклад в спецификацию исключения конструктора, потому что исключение, выброшенное из такого деструктора, std​::​terminate скорее вызовет чем экранировать конструктор ([except.throw], [except.terminate]). ]end note

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

Спецификация исключения для неявно объявленного оператора присваивания или оператора присваивания без a, noexcept-specifier который используется по умолчанию в его первом объявлении, потенциально является бросанием тогда и только тогда, когда вызов любого оператора присваивания в неявном определении является потенциально бросающим.

A deallocation function без явного указания noexcept-specifier имеет спецификацию исключения, не вызывающего выброса.

[Example:

struct A {
  A(int = (A(5), 0)) noexcept;
  A(const A&) noexcept;
  A(A&&) noexcept;
  ~A();
};
struct B {
  B() throw();
  B(const B&) = default;        // implicit exception specification is noexcept(true)
  B(B&&, int = (throw Y(), 0)) noexcept;
  ~B() noexcept(false);
};
int n = 7;
struct D : public A, public B {
    int * p = new int[n];
    // D​::​D() potentially-throwing, as the new operator may throw bad_­alloc or bad_­array_­new_­length
    // D​::​D(const D&) non-throwing
    // D​::​D(D&&) potentially-throwing, as the default argument for B's constructor may throw
    // D​::​ D() potentially-throwing
};

Более того, если бы она A​::​~A() была виртуальной, программа была бы плохо сформирована, поскольку функция, которая переопределяет виртуальную функцию из базового класса, не должна иметь спецификации потенциально вызывающего исключения, если функция базового класса имеет спецификацию исключения, не вызывающего выброса. ]end example

Спецификация исключения считается, needed когда:

  • в выражении, то функция является единственным результатом поиска или выбранный элемент из набора перегруженных функций ([basic.lookup], [over.match], [over.over]);

  • функция есть odr-used или, если она появляется в неоцененном операнде, использовалась бы odr, если бы выражение было потенциально оцененным;

  • спецификация исключения сравнивается со спецификацией другого объявления (например, явная специализация или замещающая виртуальная функция);

  • функция определена; или

  • спецификация исключения необходима для специальной функции-члена по умолчанию, которая вызывает эту функцию. [ Note: Объявление по умолчанию не требует, чтобы спецификация исключения базовой функции-члена оценивалась до тех пор, пока не потребуется неявная спецификация исключения производной функции, но явное noexcept-specifierтребует неявной спецификации исключения для сравнения. ] end note

Спецификация исключения специальной функции-члена по умолчанию оценивается, как описано выше, только при необходимости; аналогично, noexcept-specifierспециализация шаблона функции или функция-член шаблона класса создается только при необходимости.

18.5 Special functions [except.special]

Функция std​::​terminate() используется механизмом обработки исключений для устранения ошибок, связанных с самим механизмом обработки исключений. Функция std​::​current_­exception() и класс std​::​nested_­exception могут использоваться программой для захвата текущего обрабатываемого исключения.

18.5.1 The std​::​terminate() function [except.terminate]

В некоторых ситуациях необходимо отказаться от обработки исключений в пользу менее тонких методов обработки ошибок. [ Note: Эти ситуации:

  • когда механизм обработки исключений после завершения инициализации объекта исключения, но до активации обработчика исключения ([except.throw]), вызывает функцию, которая завершается через исключение, или

  • когда механизм обработки исключений не может найти обработчик для брошенного исключения ([except.handle]), или

  • когда поиск handler встречает самый внешний блок функции с не-бросанием exception specification, или

  • когда разрушение объекта во время stack unwinding завершается выдачей исключения, или

  • когда инициализация нелокальной переменной со статической или поточной продолжительностью хранения ([basic.start.dynamic]) завершается через исключение, или

  • когда уничтожение объекта со статической продолжительностью хранения или продолжительностью хранения потока завершается через exception ([basic.start.term]), или

  • когда выполнение функции регистрируется std​::​atexit или std​::​at_­quick_­exit выходят через исключение ([support.start.term]), или

  • когда a throw-expression без операнда пытается повторно вызвать исключение и исключение не обрабатывается ([except.throw]), или

  • когда функция std​::​nested_­exception​::​rethrow_­nested вызывается для объекта, который не зафиксировал исключение ([except.nested]), или

  • когда выполнение начальной функции потока завершается через исключение ([thread.thread.constr]), или

  • для параллельного алгоритма которого ExecutionPolicy задает поведение (например[execpol.seq], [execpol.par], [execpol.parunseq]), когда выполнение функции доступа к элементу ([algorithms.parallel.defns]) из параллельных выходов алгоритма с помощью исключения ([algorithms.parallel.exceptions]), или

  • когда деструктор или оператор присваивания копии вызывается для объекта типа, std​::​thread который ссылается на присоединяемый поток ([thread.thread.destr], [thread.thread.assign]), или

  • когда вызов к wait(), wait_­until()или wait_­for() функции на условном переменном ([thread.condition.condvar], [thread.condition.condvarany]) не соответствует постусловиям.

end note]

В таких случаях std​::​terminate() is called. В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматан перед вызовом std​::​terminate() . В ситуации, когда поиск handler встречает самый внешний блок функции с невыбрасыванием exception specification, определяется реализацией, разматывается ли стек, частично разматывается или не разматывается вообще перед std​::​terminate() вызовом. Во всех других ситуациях стек не должен разворачиваться до вызова std​::​terminate() . Реализации не разрешается преждевременно завершать раскручивание стека на основании определения того, что процесс раскрутки в конечном итоге вызовет вызов std​::​terminate().

18.5.2 The std​::​uncaught_­exceptions() function [except.uncaught]

Исключение считается неперехваченным после завершения инициализации exception object до завершения активации обработчика исключения ([except.handle]). Это включает разматывание стека. Если исключение генерируется повторно ([expr.throw], [propagation]), оно считается неперехваченным с момента повторного генерирования, пока не будет перехвачено повторно созданное исключение. Функция std​::​uncaught_­exceptions() возвращает количество неперехваченных исключений в текущем потоке.