Подтвердить что ты не робот

Является ли код с try-catch-rethrow эквивалентным коду без try-catch?

При каких обстоятельствах следующие два кода не эквивалентны?

{
  // some code, may throw and/or have side effects
}

try {
  // same code as above
} catch(...) {
  throw;
}

edit. Чтобы уточнить, меня не интересуют (i) отклонения от вышеприведенного шаблона (например, больше кода в блоке catch), и (ii) предназначено для того, чтобы приглашать покровительственные комментарии о правильном использование блоков try - catch.

Я ищу квалифицированный ответ со ссылкой на стандарт С++. Этот вопрос был вызван комментарием Cheers и hth. - Alf до этого моего ответа, указав без пояснения, что выше коды не эквивалентны.


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

4b9b3361

Ответ 1

Последний указывает на разматывание стека, тогда как в первом он определяется реализацией, если стек разматывается.

Соответствующие стандарты котировки (все из N3337):

[except.ctor]/1: Поскольку управление переходит из выражения throw в обработчик, деструкторы вызываются для всех автоматических объектов построенный с момента ввода блока try. Автоматические объекты уничтожаются в обратном порядке завершение их строительства.

[except.ctor]/3: Процесс вызова деструкторов для автоматических объектов, построенных по пути от блока try к throw-expression называется "разворачивание стека". [...]

[except.terminate]/2: [Когда механизм обработки исключений не может найти обработчик для исключения throw], вызывается std::terminate() (18.8.3). В ситуации, когда соответствующий обработчик не найден, он определяется реализацией независимо от того, разворачивается ли стек до того, как вызывается std::terminate(). [...]

Таким образом, если вы хотите гарантировать, что ваши автоматические объекты будут иметь свои деструкторы, выполняются в случае необработанного исключения (например, некоторое постоянное хранилище должно быть мутировано при уничтожении), тогда try {/*code*/} catch (...) {throw;} сделает это, но {/*code*/} будет нет.

Ответ 2

Элаборация на Приветствия и hth. - Комментарий Альфа:

Из http://en.cppreference.com/w/cpp/error/terminate:

std:: terminate() вызывается средой выполнения С++ при обработке исключений по одной из следующих причин:

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

Так что сброс стека может не произойти, если ваш

{
  // some code, may throw and/or have side effects
}

не находится внутри другого блока try/catch.

Пример:

struct A {
    A() {}
    ~A() { std::cout << "~A()" << std::endl; }
};

int main()
{
//    try {
        A a;
        throw 1;
//    } catch(...) {
//        throw;
//    }
}

Под coliru gcc 5.2.0 с -O2 не печатается ~A(), а при try/catch печатает.

UPD. Что касается вашего редактирования об отдельных единицах компиляции, только что протестированных с моим локальным gcc 4.8.2, поведение будет таким же: нет стека, если нет catch. Конкретный пример:

a.h:

struct A {
   A();
   ~A();
};

void foo();

a.cpp:

#include <iostream>
using namespace std;

struct A {
   A() {}
   ~A() { cout << "~A()" << endl; }
};

void foo() {
    A a;
    throw 1;
}

main.cpp:

#include "a.h"

int main () {
   //try {
    foo();
   //} catch(...) {
   //  throw;
   //}
}

Я думаю, что во время выполнения определяется catch, потому что в любом случае при запуске исключения во время выполнения программа должна искать catch. Поэтому имеет смысл выбрать, нужно ли разматывать стек во время выполнения.

Ответ 3

Семантически это эквивалентно. Я не уверен, что некоторые компиляторы не смогут оптимизировать ненужный try - catch. Я бы предпочел оставить блок try - catch. Это обычно делает код более удобным для устранения.

Ответ 4

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

Там может быть или не быть измеримое различие в производительности, связанное с накладными расходами на установку до того, как будет выполнен "некоторый код", поймать исключение (если оно есть) и реконструировать, и любую дополнительную очистку. Разница будет зависящей от компилятора и, со старыми компиляторами, потенциально значительна. С современными компиляторами разница в накладных расходах, если таковые имеются, будет несколько меньше, поскольку методы внедрения для исключений и обработки исключений улучшились.

Ответ 5

В случае, если вы поймаете основное исключение, они полностью совпадают. Вы получаете выгоду только от ловушки и реорганизации исключения, если вы делаете что-то перед броском, например, в журнал. Но вы не должны поймать Исключение. Только когда-либо поймать исключения, вы теперь можете восстановить.

Ответ 6

Некоторая значимая очистка может быть выполнена в блоке catch перед ревертом, если ресурсы не управляются как идиома RAII

       {
          // some code, may throw and/or have side effects
        }

        try {
          // same code as above
        } catch(...) {
//Some meaningful clean up can be performed here if resources not managed as RAII idiom
          throw;
        }