PVOID - Исключения

Этот раздел является переводом туториала C++ Language


  Исключения позволяют реагировать на исключительные ситуации в программах (например, ошибки времени выполнения) путем передачи управления специальным функциям, называемым обработчиками.

Для того, чтобы перехватывать исключения, часть кода помещается под контроль исключений. Это осуществляется путем помещения части кода в блок try. При возникновении исключительных ситуаций в пределах этого блока, выбрасывается исключение, передающее управление обработчику исключения. Если исключение не выбрасывается, код продолжает выполняться нормально, и все обработчики игнорируются.

Исключение выбрасывается с помощью ключевого слова throw из блока try. Обработчики исключений объявляются с помощью ключевого слова catch, которое должно быть помещено сразу после блока try:

// исключения
#include <iostream>
using namespace std;

int main () {
  try
  {
    throw 20;
  }
  catch (int e)
  {
    cout << "An exception occurred. Exception Nr. " << e << '\n';
  }
  return 0;
}
An exception occurred. Exception Nr. 20

   Код для обработки исключений заключен в блок try. В этом примере этот код просто выбрасывает исключение:

throw 20;

   Выражение throw принимает один параметр (в данном случае это целочисленное значение 20), который передается как аргумент в обработчик исключения.

Обработчик исключения объявляется с помощью ключевого слова catch сразу после закрывающей фигурной скобкой блока try. Синтаксис catch похож на обычную функцию с одним параметром. Тип этого параметра очень важен, так как сначала проверяется тип аргумента, передаваемого оператором throw, и только в случае их совпадения, исключение перехватывается обработчиком.

Можно соединить несколько обработчиков (то есть операторов catch); каждый из них с разным типом параметра. Выполняется только тот обработчик, аргумент которого совпадает с типом исключения, указанным в операторе throw.

Если в качестве параметра catch используется многоточие (...), то обработчик будет перехватывать любое исключение независимо от типа выброшенного исключения. Это может быть использовано как обработчик по умолчанию, который перехватывает все исключения, которые не перехвачены другими обработчиками:

try {
  // здесь код
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }	

   В этом случае, последний обработчик будет перехватывать любое исключение, кроме исключений типа int и char.

После того, как исключение обработано, выполнение программы возобновляется после блока try-catch, а не после оператора throw!

Также возможно вложение блоков try-catch во внешний блок try. В таких случаях мы имеем возможность того, чтобы внутренний блок catch перенаправлял исключение на внешний уровень. Это делается оператором throw; без аргументов. Например: 

try {
  try {
      // здесь код
  }
  catch (int n) {
      throw;
  }
}
catch (...) {
  cout << "Exception occurred";
}	

  Спецификация исключений


Старый код мог содержать динамические спецификации исключений. Сейчас они не рекомендуемы в C++, но все еще поддерживаются. Динамические спецификации исключений следуют за объявлением функции, добавляя к ним спецификатор throw. Например:

double myfunction (char param) throw (int);	

   Этот код объявляет функцию myfunction, которая принимает один аргумент типа char и возвращает значение типа double. Если эта функция выбрасывает исключение какого-либо типа, кроме int, функция вызывает std::unexpected, вместо поиска обработчика или вызова std::terminate.

Если этот спецификатор throw остается пустым без указания типа, это означает, что вызывается std::unexpected для любого исключения. Функции без спецификатора throw (обычные функции) никогда не вызывают std::unexpected и следуют обычному способу поиска обработчика исключения.

int myfunction (int param) throw(); // все исключения вызывают unexpected
int myfunction (int param);         // нормальная обработка исключений 

   Стандартные исключения


Стандартная библиотека C++ предоставляет базовый класс, специально разработанный для объявления объектов, которые будут выброшены в качестве исключений. Он называется std::exception и определен в заголовке <exception>. Этот класс имеет виртуальный метод what, который возвращает последовательность символов с завершающим '\0' (типа char*), и который может быть переопределен в производных классах, чтобы содержать необходимое описание исключения.

// использование стандартных исключений
#include <iostream>
#include <exception>
using namespace std;

class myexception: public exception
{
  virtual const char* what() const throw()
  {
    return "My exception happened";
  }
} myex;

int main () {
  try
  {
    throw myex;
  }
  catch (exception& e)
  {
    cout << e.what() << '\n';
  }
  return 0;
}
My exception happened.

Мы разместили обработчик, который перехватывает исключение объектов по ссылке (Обратите внимание на знак амперсанда & после типа), поэтому он перехватывает также и классы, производные от exception, такие как объект myex типа myexception.

Все исключения, выбрасываемые компонентами стандартной библиотеки C++, выбрасывают исключения, производные от класса exception. К ним относятся:

 Исключение  Описание
bad_alloc  выбрасывается new в случае ошибки выделения памяти
bad_cast  выбрасывается dynamic_cast при ошибке приведения типов
bad_exception  выбрасывается определенными динамическими спецификаторами исключения
bad_typeid  выбрасывается typeid
bad_function_call  выбрасывается пустыми объектами function
bad_weak_ptr  выбрасывается shared_ptr, когда передан неправильный weak_ptr

 

   Заголовочный файл <exception> определяет два общих типа исключения, также производные от exception, которые могут быть унаследованы пользовательскими исключениями для сообщения об ошибках:

 Исключение  Описание
logic_error  ошибка, связанная с внутренней логикой работы программы
runtime_error  ошибки, обнаруженные во время выполнения

 

Типичный пример, когда необходимо перехватывать стандартные исключения, это выделение памяти:

// стандартное исключение bad_alloc
#include <iostream>
#include <exception>
using namespace std;

int main () {
  try
  {
    int* myarray= new int[1000];
  }
  catch (exception& e)
  {
    cout << "Standard exception: " << e.what() << endl;
  }
  return 0;
}

   Исключение, которое может быть перехвачено обработчиком исключения в этом примере, это bad_alloc. bad_alloc является производным от стандартного базового класса exception, поэтому он может быть перехвачен (перехват по ссылке exception& позволяет перехватывать исключения всех производных типов).