Поддерживает ли C ++ блоки finally ?
Что такое идиома RAII ?
В чем разница между C ++ идиомой RAII и C # 'использованием' выражения ?
Поддерживает ли C ++ блоки finally ?
Что такое идиома RAII ?
В чем разница между C ++ идиомой RAII и C # 'использованием' выражения ?
Ответы:
Нет, C ++ не поддерживает блоки finally. Причина в том, что C ++ вместо этого поддерживает RAII: «Приобретение ресурсов - это инициализация» - плохое название † для действительно полезной концепции.
Идея состоит в том, что деструктор объекта отвечает за освобождение ресурсов. Когда объект имеет автоматическую продолжительность хранения, деструктор объекта будет вызываться при выходе из блока, в котором он был создан, даже когда этот блок выходит при наличии исключения. Вот объяснение Бьярне Страуструпа по теме.
Обычное использование RAII - блокировка мьютекса:
// A class with implements RAII
class lock
{
mutex &m_;
public:
lock(mutex &m)
: m_(m)
{
m.acquire();
}
~lock()
{
m_.release();
}
};
// A class which uses 'mutex' and 'lock' objects
class foo
{
mutex mutex_; // mutex for locking 'foo' object
public:
void bar()
{
lock scopeLock(mutex_); // lock object.
foobar(); // an operation which may throw an exception
// scopeLock will be destructed even if an exception
// occurs, which will release the mutex and allow
// other functions to lock the object and run.
}
};
RAII также упрощает использование объектов в качестве членов других классов. При уничтожении класса-владельца ресурс, управляемый классом RAII, освобождается, потому что в результате вызывается деструктор для класса, управляемого RAII. Это означает, что когда вы используете RAII для всех членов класса, которые управляют ресурсами, вы можете обойтись без очень простого, может даже стандартного деструктора для класса владельца, так как ему не нужно вручную управлять временем жизни его члена. , (Благодаря Майку Б за указание на это.)
Для тех, кто знаком с C # или VB.NET, вы можете признать, что RAII похож на детерминированное уничтожение .NET с использованием операторов IDisposable и «using» . Действительно, два метода очень похожи. Основное отличие состоит в том, что RAII детерминистически освобождает любой тип ресурса, включая память. При реализации IDisposable в .NET (даже на языке .NET C ++ / CLI) ресурсы будут освобождаться детерминистически, за исключением памяти. В .NET память не освобождается детерминистически; память освобождается только во время циклов сбора мусора.
† Некоторые люди считают, что «Разрушение - это отказ от ресурсов» - более точное название идиомы RAII.
В C ++, наконец, НЕ требуется из-за RAII.
RAII переносит ответственность за безопасность исключений от пользователя объекта к разработчику (и исполнителю) объекта. Я бы сказал, что это правильное место, так как вам нужно только один раз получить правильную безопасность исключений (при разработке / реализации). Используя, наконец, вам нужно корректировать безопасность исключений каждый раз, когда вы используете объект.
Также ИМО код выглядит аккуратнее (см. Ниже).
Пример:
Объект базы данных. Чтобы убедиться, что соединение с БД используется, оно должно быть открыто и закрыто. С помощью RAII это можно сделать в конструкторе / деструкторе.
void someFunc()
{
DB db("DBDesciptionString");
// Use the db object.
} // db goes out of scope and destructor closes the connection.
// This happens even in the presence of exceptions.
Использование RAII делает использование объекта БД корректно очень простым. Объект БД будет корректно закрываться при использовании деструктора, независимо от того, как мы пытаемся его использовать.
void someFunc()
{
DB db = new DB("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
// Can not rely on finaliser.
// So we must explicitly close the connection.
try
{
db.close();
}
catch(Throwable e)
{
/* Ignore */
// Make sure not to throw exception if one is already propagating.
}
}
}
При использовании наконец правильное использование объекта делегируется пользователю объекта. т.е. пользователь объекта обязан правильно закрыть соединение с БД. Теперь вы можете утверждать, что это можно сделать в финализаторе, но ресурсы могут иметь ограниченную доступность или другие ограничения, и, таким образом, вы, как правило, хотите контролировать освобождение объекта и не полагаться на недетерминированное поведение сборщика мусора.
Также это простой пример.
Когда у вас есть несколько ресурсов, которые должны быть освобождены, код может усложниться.
Более подробный анализ можно найти здесь: http://accu.org/index.php/journals/236
// Make sure not to throw exception if one is already propagating.
Для деструкторов C ++ важно не выбрасывать исключения также по этой самой причине.
RAII обычно лучше, но вы можете легко иметь окончательную семантику в C ++. Используя небольшое количество кода.
Кроме того, основные принципы C ++ дают наконец.
Вот ссылка на реализацию GSL Microsoft и ссылка на реализацию Мартина Моена
Бьярн Страуструп несколько раз говорил, что все, что есть в GSL, в конечном итоге означает перейти в стандарт. Так что это должен быть ориентированный на будущее способ использовать наконец .
Вы можете легко реализовать себя, если хотите, продолжайте читать.
В C ++ 11 RAII и лямбда-выражения позволяют сделать генерал окончательно:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
пример использования:
#include <iostream>
int main() {
int* a = new int;
auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
std::cout << "doing something ...\n"; }
вывод будет:
doing something...
leaving the block, deleting a!
Лично я использовал это несколько раз, чтобы обеспечить закрытие дескриптора файла POSIX в программе на C ++.
Обычно лучше иметь реальный класс, который управляет ресурсами и, таким образом, избегает любых утечек, но это, наконец , полезно в тех случаях, когда создание класса звучит как перебор.
Кроме того, мне это нравится больше, чем другим языкам, в конце концов, потому что при естественном использовании вы пишете закрывающий код рядом с открывающим кодом (в моем примере new и delete ), а разрушение следует за конструированием в порядке LIFO, как обычно в C ++. Единственным недостатком является то, что вы получаете автоматическую переменную, которую вы на самом деле не используете, а лямбда-синтаксис делает ее немного шумной (в моем примере в четвертой строке только слово finally и {} -блок справа имеют смысл, остальное по сути шум).
Другой пример:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
Элемент disable полезен, если оператор finally должен вызываться только в случае сбоя. Например, вам нужно скопировать объект в три разных контейнера, вы можете настроить функцию finally для отмены каждой копии и отключения после того, как все копии будут успешными. Поступая так, если уничтожение не может бросить, вы гарантируете сильную гарантию.
отключить пример:
//strong guarantee
void copy_to_all(BIGobj const& a) {
first_.push_back(a);
auto undo_first_push = finally([first_&] { first_.pop_back(); });
second_.push_back(a);
auto undo_second_push = finally([second_&] { second_.pop_back(); });
third_.push_back(a);
//no necessary, put just to make easier to add containers in the future
auto undo_third_push = finally([third_&] { third_.pop_back(); });
undo_first_push.disable();
undo_second_push.disable();
undo_third_push.disable(); }
Если вы не можете использовать C ++ 11, у вас все еще может быть наконец , но код становится немного длиннее. Просто определите структуру только с помощью конструктора и деструктора, конструктор берет ссылки на все, что нужно, а деструктор выполняет необходимые действия. Это в основном то, что делает лямбда, делается вручную.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
это в основном то же самое, что и популярная ScopeGuard
идиома, только с другим именем.
Помимо упрощения очистки с помощью стековых объектов, RAII также полезен, поскольку такая же «автоматическая» очистка происходит, когда объект является членом другого класса. Когда класс-владелец уничтожается, ресурс, управляемый классом RAII, очищается, потому что в результате вызывается dtor для этого класса.
Это означает, что когда вы достигаете нирваны RAII, и все члены в классе используют RAII (например, умные указатели), вы можете получить очень простой (возможно, даже стандартный) dtor для класса владельца, так как ему не нужно вручную управлять его время жизни ресурса участника.
почему даже управляемые языки обеспечивают блок finally, несмотря на то, что ресурсы все равно автоматически освобождаются сборщиком мусора?
На самом деле, языки, основанные на сборщиках мусора, нуждаются в «наконец-то» больше. Сборщик мусора не уничтожает ваши объекты своевременно, поэтому на него нельзя полагаться для правильного устранения проблем, не связанных с памятью.
Что касается динамически распределяемых данных, многие утверждают, что вы должны использовать умные указатели.
Тем не мение...
RAII переносит ответственность за безопасность исключений с пользователя объекта на дизайнера
К сожалению, это его собственный недостаток. Старые привычки программирования на С сильно умирают. Когда вы используете библиотеку, написанную на C или в стиле C, RAII не будет использоваться. Если не считать переписывания всего API-интерфейса, то именно с этим вам и придется работать. Тогда отсутствие «наконец» действительно кусает.
CleanupFailedException
. Есть ли какой-нибудь вероятный способ достичь такого результата с помощью RAII?
SomeObject.DoSomething()
метод и хочет знать, успешно ли он (1), (2) не получен без побочных эффектов , (3) - с побочными эффектами, с которыми готовый справиться вызов или (4) не удалось с побочными эффектами, с которыми вызывающий абонент не может справиться. Только звонящий узнает, с какими ситуациями он может и не может справиться; то, что нужно звонящему, это способ узнать ситуацию. Жаль, что нет стандартного механизма предоставления наиболее важной информации об исключении.
Еще одна эмуляция блока finally с использованием лямбда-функций C ++ 11
template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
try
{
code();
}
catch (...)
{
try
{
finally_code();
}
catch (...) // Maybe stupid check that finally_code mustn't throw.
{
std::terminate();
}
throw;
}
finally_code();
}
Будем надеяться, что компилятор оптимизирует код выше.
Теперь мы можем написать код так:
with_finally(
[&]()
{
try
{
// Doing some stuff that may throw an exception
}
catch (const exception1 &)
{
// Handling first class of exceptions
}
catch (const exception2 &)
{
// Handling another class of exceptions
}
// Some classes of exceptions can be still unhandled
},
[&]() // finally
{
// This code will be executed in all three cases:
// 1) exception was not thrown at all
// 2) exception was handled by one of the "catch" blocks above
// 3) exception was not handled by any of the "catch" block above
}
);
Если вы хотите, вы можете обернуть эту идиому в макрос «попробуй - наконец»:
// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};
#define begin_try with_finally([&](){ try
#define finally catch(never_thrown_exception){throw;} },[&]()
#define end_try ) // sorry for "pascalish" style :(
Теперь блок "finally" доступен в C ++ 11:
begin_try
{
// A code that may throw
}
catch (const some_exception &)
{
// Handling some exceptions
}
finally
{
// A code that is always executed
}
end_try; // Sorry again for this ugly thing
Лично мне не нравится «макро» версия идиомы finally, и я бы предпочел использовать чистую функцию with_finally, хотя синтаксис в этом случае более громоздкий.
Вы можете проверить код выше здесь: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Если вам нужен блок finally в вашем коде, тогда ограниченные области действия или ON_FINALLY / ON_EXCEPTION макросы будут , вероятно , лучше соответствовать вашим потребностям.
Вот краткий пример использования ON_FINALLY / ON_EXCEPTION:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
...
Извините, что выкопал такой старый поток, но в следующих рассуждениях есть серьезная ошибка:
RAII переносит ответственность за безопасность исключений от пользователя объекта к разработчику (и исполнителю) объекта. Я бы сказал, что это правильное место, так как вам нужно только один раз получить правильную безопасность исключений (при разработке / реализации). Используя, наконец, вам нужно корректировать безопасность исключений каждый раз, когда вы используете объект.
Чаще всего вам приходится иметь дело с динамически размещаемыми объектами, динамическим числом объектов и т. Д. В блоке try некоторый код может создавать множество объектов (количество которых определяется во время выполнения) и сохранять указатели на них в списке. Теперь это не экзотический сценарий, но очень распространенный. В этом случае вы хотели бы написать что-то вроде
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
finally
{
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
}
Конечно, сам список будет уничтожен при выходе из области видимости, но это не приведет к очистке созданных вами временных объектов.
Вместо этого вы должны идти по безобразному пути:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
catch(...)
{
}
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
Кроме того: почему даже управляемые языки обеспечивают блок finally, несмотря на то, что ресурсы все равно автоматически удаляются сборщиком мусора?
Подсказка: есть нечто большее, что вы можете сделать с «окончанием», чем просто освобождение памяти.
new
не возвращает NULL, вместо этого
std::shared_ptr
и std::unique_ptr
непосредственно в stdlib.
FWIW, Microsoft Visual C ++ поддерживает try, наконец, и он исторически использовался в приложениях MFC как метод отлова серьезных исключений, которые в противном случае могли бы привести к сбою. Например;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
Я использовал это в прошлом, чтобы делать такие вещи, как сохранение резервных копий открытых файлов перед выходом. Некоторые параметры отладки JIT нарушают этот механизм.
Как указано в других ответах, C ++ может поддерживать finally
подобную функциональность. Реализация этой функциональности, которая, вероятно, наиболее близка к тому, чтобы быть частью стандартного языка, - это та, которая сопровождает основные руководящие принципы C ++ , набор лучших практик по использованию C ++, отредактированный Бьярном Стоуструпом и Хербом Саттером. Реализацияfinally
является частью библиотеки Guidelines поддержки (GSL). Повсюду в Руководстве рекомендуется использовать его finally
при работе с интерфейсами старого стиля, и у него также есть собственное руководство под названием Использовать объект final_action для выражения очистки, если нет подходящего дескриптора ресурса .
Таким образом, C ++ не только поддерживает finally
, но и рекомендуется использовать его во многих типичных случаях.
Пример использования реализации GSL будет выглядеть так:
#include <gsl/gsl_util.h>
void example()
{
int handle = get_some_resource();
auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });
// Do a lot of stuff, return early and throw exceptions.
// clean_that_resource will always get called.
}
Внедрение и использование GSL очень похоже на ответ в ответе Паоло Больцони . Единственное отличие состоит в том, что объект, созданный с помощью, gsl::finally()
не имеет disable()
вызова. Если вам нужна эта функциональность (скажем, для возврата ресурса после того, как он собран, и исключений не должно быть), вы можете предпочесть реализацию Paolo. В противном случае использование GSL настолько близко к использованию стандартизированных функций, насколько это возможно.
Не совсем, но вы можете эмулировать их в некоторой степени, например:
int * array = new int[10000000];
try {
// Some code that can throw exceptions
// ...
throw std::exception();
// ...
} catch (...) {
// The finally-block (if an exception is thrown)
delete[] array;
// re-throw the exception.
throw;
}
// The finally-block (if no exception was thrown)
delete[] array;
Обратите внимание, что блок finally может сам выдать исключение до того, как исходное исключение будет переброшено, тем самым отбрасывая исходное исключение. Это то же самое поведение, что и в Java-блоке finally. Кроме того, вы не можете использовать return
внутри блоков try & catch.
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
блока.
Я придумал finally
макрос , который может быть использован почти как ¹ по finally
ключевым словам в Java; он использует std::exception_ptr
и друзей, лямбда-функции и std::promise
, следовательно, требует C++11
или выше; он также использует составное выражение GCC, которое также поддерживается clang.
ВНИМАНИЕ : в более ранней версии этого ответа использовалась другая реализация концепции с гораздо большим количеством ограничений.
Сначала давайте определим вспомогательный класс.
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
Тогда есть фактический макрос.
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
Это можно использовать так:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
Использование std::promise
делает его очень простым для реализации, но, вероятно, также вносит немало ненужных накладных расходов, которых можно избежать, реализуя только необходимые функции std::promise
.
AV CAVEAT: есть несколько вещей, которые не работают так же, как в java-версии finally
. С верхней части моей головы:
break
оператора из блоков try
and catch()
, поскольку они живут в лямбда-функции;catch()
блок try
: это требование C ++;try
and нет возврата catch()'s
, компиляция завершится неудачно, поскольку finally
макрос расширится до кода, который захочет вернуть a void
. Это может быть, эээ, аннулируется ред при наличии finally_noreturn
макрокоманды сортов.В общем, я не знаю, буду ли я когда-либо использовать этот материал сам, но было весело играть с ним. :)
catch(xxx) {}
блок в начале finally
макроса, где xxx - фиктивный тип только для того, чтобы иметь хотя бы один блок catch.
catch(...)
, не так ли?
xxx
в частном пространстве имен, которое никогда не будет использоваться.
У меня есть сценарий использования, где я считаю, что он finally
должен быть вполне приемлемой частью языка C ++ 11, так как я считаю, что его легче читать с точки зрения потока. Мой пример использования - цепочка потоков «потребитель / производитель», где nullptr
в конце цикла посылается дозорный, чтобы закрыть все потоки.
Если бы C ++ это поддерживал, вы бы хотели, чтобы ваш код выглядел так:
extern Queue downstream, upstream;
int Example()
{
try
{
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
finally {
downstream.push(nullptr);
}
}
Я думаю, что это более логично, чем помещать ваше объявление finally в начале цикла, так как это происходит после выхода из цикла ... но это заблуждение, потому что мы не можем сделать это в C ++. Обратите внимание, что очередь downstream
подключена к другому потоку, поэтому вы не можете поместить дозорный push(nullptr)
в деструктор, downstream
потому что он не может быть уничтожен на этом этапе ... он должен оставаться в живых, пока другой поток не получитnullptr
.
Итак, вот как использовать класс RAII с лямбдой, чтобы сделать то же самое:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
и вот как вы это используете:
extern Queue downstream, upstream;
int Example()
{
Finally atEnd([](){
downstream.push(nullptr);
});
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
Как утверждают многие, решение состоит в том, чтобы использовать функции C ++ 11, чтобы избежать блоков finally. Одной из особенностей является unique_ptr
.
Вот ответ Мефана, написанный с использованием шаблонов RAII.
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
Еще одно введение в использование unique_ptr с контейнерами стандартной библиотеки C ++ здесь
Я хотел бы предоставить альтернативу.
Если вы хотите, чтобы блок finally вызывался всегда, просто поместите его после последнего блока catch (что, вероятно, должно быть catch( ... )
для перехвата неизвестного исключения)
try{
// something that might throw exception
} catch( ... ){
// what to do with uknown exception
}
//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp();
Если вы хотите, чтобы блок finally был последним, что было сделано при возникновении какого-либо исключения, вы можете использовать логическую локальную переменную - перед запуском установите значение false и присвойте значение true в самом конце блока try, затем после проверки блока catch для переменной стоимость:
bool generalAppState = false;
try{
// something that might throw exception
//the very end of try block:
generalAppState = true;
} catch( ... ){
// what to do with uknown exception
}
//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
doSomeCleanUpOfDirtyEnd();
}
//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
cleanEnd();
}
Я также думаю, что RIIA не является полностью полезной заменой для обработки исключений и наличия наконец. Кстати, я также думаю, что RIIA это плохое имя во всем мире. Я называю эти типы классов «уборщиками» и использую их МНОГО. В 95% случаев они не инициализируют и не получают ресурсы, они применяют некоторые изменения на определенной основе или принимают что-то уже настроенное и проверяют, уничтожено ли оно. Это официальное название одержимого интернетом, которое я оскорбляю, даже если предположить, что мое имя может быть лучше.
Я просто не думаю, что разумно требовать, чтобы в каждой сложной настройке какого-то специального списка вещей был написан класс, содержащий его, чтобы избежать сложностей при очистке всего этого в случае необходимости перехватывать несколько типы исключений, если что-то идет не так в процессе. Это привело бы ко многим специальным классам, которые просто не были бы необходимы иначе.
Да, это хорошо для классов, которые предназначены для управления конкретным ресурсом, или для общих классов, которые предназначены для обработки набора похожих ресурсов. Но, даже если все вещи имеют такие оболочки, координация очистки может быть не просто вызовом деструкторов в обратном порядке.
Я думаю, что для C ++ имеет смысл иметь наконец-то. Я имею в виду, господи, за последние десятилетия на него было наклеено так много битов и бобов, что кажется, что странные люди внезапно станут консервативными из-за чего-то вроде, наконец, что может быть весьма полезным и, вероятно, не таким сложным, как некоторые другие вещи, которые были добавил (хотя это только предположение с моей стороны.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
не делает.