2015-11-13 4 views
23

При каких обстоятельствах следующие два кода не эквивалентны?Код с try-catch-rethrow эквивалентен коду без try-catch?

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

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

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

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


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

+0

* Есть ли там обстоятельства, когда они не эквивалентны? – user2079303

+1

@ пользователь2079303 это как раз мой вопрос. – Walter

+0

О, я вижу. Я только прочитал вопрос, который спрашивает, когда они не эквивалентны, что подразумевает, что такие случаи существуют. Я хотел бы знать, что такое @ Cheersandhth.-Alf имеет в виду. – user2079303

ответ

17

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

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

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

[except.ctor]/3: Процесс вызова деструкторов для автоматического объекты, построенные на пути из попробовать блок на вбрасывания выражение называется «стек разматывания.» [...]

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

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

+0

Но как компилятор может определить, будет ли когда-либо обнаружен обработчик исключений, скажем, из вызывающей функции? – Walter

+2

@Walter Я тестировал это через единицы перевода, используя clang, и '{/ * code *}' не вызывал деструктор, но версия rethrow. Я не знаю достаточно о реализации исключений, чтобы сказать, как это делается. – TartanLlama

+1

@Walter: Это не компилятор, но система времени выполнения определяет, можно ли найти обработчик исключений. Поскольку деструкторы, вызываемые во время разматывания стека, не могут изменить обработчик (если есть), который будет активирован, система свободна (я думаю, что это значение '[except.terminate]/2'), чтобы разматывать и разрушать при поиске обработчика , Но в качестве альтернативы он может сначала завершить поиск и только начать разматывать после обнаружения обработчика. В последнем случае уничтожение вообще не будет происходить в случае, если никакой обработчик не найден. –

3

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

+0

Можете ли вы поддержать свою претензию (что коды эквивалентны) с цитатой форму стандарта? – Walter

+0

@Walter обратите внимание, что я написал, что он «семантически эквивалентен». В стандарте AFAIK не указано это явно, но это видно из описаний семантики для исключений и обработки исключений. – cdonat

1

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

+1

Исключено «основное исключение». Вероятно, вы думаете о возможности языка, отличного от C++. – Peter

+0

@PalleDue No. В случае, если они только * поймают основное Исключение *, это не будет полностью одинаковым. Код в вопросе ловит * все * брошенные объекты. – user2079303

+0

Я отвечал на вопрос, думая, что это C#. До редактирования не было намека на то, что это был C++. Извини за это. –

-1

Некоторые значимые очистки могут быть выполнены в уловов блоке перед Rethrow, если ресурсы не удалось, как 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; 
     } 
+0

Это не отвечает на вопрос. – Walter

+0

@Walter Я просто попытался указать на то обстоятельство, что эти две конструкции кода могут быть разными. – NightFurry

+0

Нет, вы этого не сделали, поскольку вы изменили код. – Walter

3

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

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

+0

Я не уверен, что вы ответите на мой вопрос. Компилятор не может быть уверен в том, находятся ли эти коды в блоке try-catch, и как он может избежать разворачивания стека ??? – Walter

+1

@Walter компилятор может сгенерировать код для * walk * (не разворачивать) стек вызовов, чтобы найти обработчик исключений, и если он не найдет один акт соответственно. – jepio

+0

@jepio Ваш комментарий ближе всего к удовлетворительному ответу (см. Также мое недавнее редактирование OP). - Не могли бы вы ответить на этот вопрос? – Walter

7

Elaboratring на Cheers and hth. - Alf's comment:

От http://en.cppreference.com/w/cpp/error/terminate:

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

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; 
// } 
} 

Under coliru's gcc 5.2.0 with -O2 не печатает ~A(), в то время как с try/catch делает печать.

UPD: относительно вашего редактирования об отдельных единицах компиляции, просто протестирован с моей местной НКОЙ 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. Поэтому имеет смысл выбрать, нужно ли раскручивать стек во время выполнения.

+0

А, так что в случае, когда исключение вообще не поймано, это не так. Можете ли вы также рассказать о том, как разматывание стека полезно, когда программа все равно прекратится? – user2079303

+1

@ user2079303, очевидно, чтобы сделать любой код очистки. Просто быстрый пример: у вас есть удаленный сервер, который может принимать только одно соединение вовремя. У вас будет какой-то объект «Connection» в вашей программе, и его деструктор закроет соединение, чтобы сервер был готов принять новое соединение. – Petr

+3

@ user2079303, или даже проще: закрыть поток вывода файла, чтобы все данные, которые буферизовались, фактически попадали на диск. – Petr

Смежные вопросы