Обработка исключений является важной частью любой крупномасштабной разработки программного обеспечения, когда взаимодействует независимо разработанная часть программы. C++, как и многие другие основные потоковые языки программирования, предоставляет конструкции try и catch для обработки исключений. На высоком уровне обработка исключений включает следующие шаги.

  1. Вы заключаете код, подверженный исключению, в блок try{..}.
  2. За этим блоком try{..} следует серия блоков catch(){..}, то есть обработчиков, в которых вы полностью или частично обрабатываете исключение.
  3. Если возникает исключение, раскручивание стека начинается до тех пор, пока не будет найден соответствующий блок catch(){..}, после чего выполнение программы возобновляется в соответствующем обработчике.
  4. Если исключение частично обработано, оно может быть вызвано снова и привести к завершению программы, если соответствующий блок catch(){..} не будет найден. В противном случае выполнение возобновляется после блока try{..}.

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

Каждый раз, когда создается объект класса A, его конструктор должен вызывать какой-либо конструктор класса B и класса C. Если этого не сделать, компилятор вставит вызовы конструкторов по умолчанию классов B и C. Но что, если нам придется вызывать конструкторы не по умолчанию? из B и C или инициализировать ссылочный элемент A? Наиболее эффективным способом является использование списка инициализации, как показано ниже.

Во время выполнения списка инициализации существует вероятность исключения, т.е. до того, как управление программой войдет внутрь тела конструктора. Если да, то что происходит с создаваемым объектом? Будет ли вызван деструктор? Будет ли частичный объект возвращен вызывающему методу? Давайте углубимся.

Для обработки исключений, возникающих из списка инициализации, C++ предоставляет блок function try. Их также можно использовать с обычными функциями и деструкторами; но такие случаи редки и имеют некоторые особенности.

Вот как выглядит блок function try

Блок Function try подобен обычному блоку try{..}, но не входит в тело функции. Вместо этого он упаковывает определение функции и список инициализации в случае конструкторов, связывая серию catch(){}clauses со всем телом функции. Несмотря на то, что блок function try можно использовать с обычными функциями и деструкторами, более целесообразно использовать их с конструкторами, имеющими список инициализации. Почему? Мы увидим позже в посте.

Есть несколько ограничений с блоком function try.

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

2. Поскольку невозможно узнать, как далеко в списке инициализации возникло исключение, все созданные до сих пор подобъекты-члены или базовые классы уничтожаются перед входом в обработчик. Более того, доступ к нестатическим членам или базовому классу недоступен, поэтому вы не можете пытаться выполнить какое-либо восстановление. Это преднамеренное поведение.

Итак, когда использовать блок function try? Или лучше не использовать.

  1. Поскольку рекомендуется, чтобы деструктор не выдавал никаких исключений; достижение конца обработчика catch блока try функции в деструкторе повторно выдает то же самое исключение, хотя допускается оператор return. Исключение из деструктора может вызвать утечку в вашей программе. Это связано с тем, что в случае наследования, когда порядок вызовов деструктора - от большинства производных классов к базовому классу, любое исключение, созданное во время этих вызовов, оставит объект частично разрушенным.
  2. Достижение конца обычной функции так же хорошо, как return;, если тип возвращаемого значения функции void; в противном случае поведение не определено.
  3. Более того, их использование с void-функциями и конструкторами без списка инициализации ничего не дает. Вместо этого лучше использовать обычный блок try{..} catch(){..} внутри тела.

Не используйте блок function try для перехвата исключений, генерируемых внутри конструктора или тела функции. Если вы попадаете в обработчик catch блока function try из-за исключения (например, bad_alloc) внутри конструктора или тела функции, рекомендуется обрабатывать их с помощью обычного блока try{..}catch(){..} внутри тела функции. Для выделения памяти рекомендуется использовать RAII; но если вам нужно выделить память разными способами, делайте это внутри тела конструктора; никогда в списке инициализации. Кроме того, в случае, если вы хотите продолжить создание объекта, даже если создание некоторых его частей не удается, используйте указатели на подобъект класса вместо подобъектов в качестве членов. Таким образом, эти нулевые указатели будут отражать сбой построения подобъектов.

Таким образом, единственный сценарий, в котором вы должны использовать блок function try, — это когда в списке инициализации вашего конструктора есть хотя бы один вызов определяемого пользователем конструктора, который может вызывать исключение. Поскольку такое исключение не может быть обработано, функция tryblock не предназначена для восстановления после сбоя и попытки восстановить объект. Максимум, вы можете заменить выбрасываемое исключение и вызвать некоторые полезные побочные эффекты, такие как регистрация сбоя. Кроме того, использование блока function try с обычными функциями и деструктором встречается редко.