При использовании языка, в котором есть try/catch/finally, есть еще аргументы D сбой/успех/выход? D, похоже, не может окончательно объяснить, почему эти утверждения используются в D. Но с таким языком, как С#, это полезно? Я разрабатываю язык, поэтому, если вижу много профи, я его добавлю.
Требуется ли отказ или необходимость/выход D-области?
Ответ 1
scope(X)
не требуется таким же образом, что for
не требуется, если у вас есть if
и goto
.
Вот парафированный пример из некоторого кода, который я писал сегодня:
sqlite3* db;
sqlite3_open("some.db", &db);
scope(exit) sqlite3_close(db);
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt);
scope(exit) sqlite3_finalize(stmt);
// Lots of stuff...
scope(failure) rollback_to(current_state);
make_changes_with(stmt);
// More stuff...
return;
Сравните это с использованием try/catch:
sqlite3* db;
sqlite3_open("some.db", &db);
try
{
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, "SELECT * FROM foo;", &stmt);
try
{
// Lots of stuff...
try
{
make_changes_with(stmt);
// More stuff...
}
catch( Exception e )
{
rollback_to(current_state);
throw;
}
}
finally
{
sqlite3_finalize(stmt);
}
}
finally
{
sqlite3_close(db);
}
Код превратился в spaghetti, распространяя восстановление ошибок по всему магазину и заставляя уровень отступов для каждого блока try. Версия с использованием области (X), на мой взгляд, значительно читаема и понятна.
Ответ 2
try/catch/, наконец, создает уровень вложенности; Охранники не имеют. Кроме того, они позволяют вам писать код очистки в том же "area", что и код выделения, поэтому больше не нужно "открывать файл, прокручивать до конца функции, закрывать файл, прокручивать вверх до функции".
В сущности, это просто более удобное выражение обработки try/catch/finally - все, что вы можете сделать с try/catch/, наконец, вы можете сделать с защитой области и наоборот.
Стоит ли это того? Я фанат D (так, предвзятый), но я бы сказал определенно.
Ответ 3
Отказ от ответственности Я тоже мальчик-фанат D.
someRiskyFunctionThatMayThrow();
lock();
/* we have definitly got the lock so lets active
a piece of code for exit */
scope(exit)
freelock();
По сравнению с:
try
{
someRiskyFunctionThatMayThrow();
lock();
}
finally
{
freeLockIfNotGot();
}
Ответ 4
Отличие от отказа-выхода из успеха-выхода весьма полезно некоторое время - у меня нет реального опыта в мире с D, но оператор Python with
также позволяет это, и я нахожу его очень полезным, например, для либо зафиксировать или отменить транзакцию БД, которая была открыта в защищенной части тела.
Когда я объяснил эту новую функцию Python (она была довольно давно;-) для друзей и коллег, которые являются гуру на С++ и Java, я обнаружил, что они сразу поняли и увидели интерес к такой функции ( У Python тоже есть finally
, но это не помогает отличить успех от отказа, как и на других языках [или С++ "RAII уничтожение автоматических переменных в эквиваленте блока]).
Ответ 5
Стоит отметить, что для С++ доступны также область (выход), область (сбой) и область (успех).
- Для области (выхода) существует библиотека Boost.ScopeExit.
- Для области (сбой) и области (успех) есть библиотека stack_unwinding.
Поддерживается следующий синтаксис: case 1:
try
{
int some_var=1;
cout << "Case #1: stack unwinding" << endl;
scope(exit)
{
cout << "exit " << some_var << endl;
++some_var;
};
scope(failure)
{
cout << "failure " << some_var << endl;
++some_var;
};
scope(success)
{
cout << "success " << some_var << endl;
++some_var;
};
throw 1;
} catch(int){}
печатает:
Case #1: stack unwinding
failure 1
exit 2
Случай 2:
{
int some_var=1;
cout << "Case #2: normal exit" << endl;
scope(exit)
{
cout << "exit " << some_var << endl;
++some_var;
};
scope(failure)
{
cout << "failure " << some_var << endl;
++some_var;
};
scope(success)
{
cout << "success " << some_var << endl;
++some_var;
};
}
печатает:
Case #2: normal exit
success 1
exit 2
Ответ 6
@DK. Следует отметить, что в С++ (и Java, я думаю) вы можете легко использовать "анонимный" класс для выполнения той же задачи, что и область (выход):
int some_func()
{
class _dbguard { sqlite3* db;
_dbguard(const _dbguard&); _dbguard& operator=(const _dbguard&);
public:
_dbguard(const char* dbname) { sqlite3_open(dbname, &db);}
~_dbguard() {sqlite3_close(db);}
operator sqlite3*() { return db; }
} db("dbname");
...
}
И если вы делали это более одного раза, вы сразу же превратили бы его в полноценный класс для обработки RAII для вас. Это так просто писать, я не могу представить себе программу на С++, которая использует sqlite (как используется в примере) без создания таких классов, как CSqlite_DB и CSqlite_Stmt. Фактически оператор sqlite3 *() должен быть anathama, и полная версия будет иметь только методы, которые предоставляют инструкции:
class CSqlite3_DB {
...
CSqlite3_Stmt Prepare(const std::string& sql) {
sqlite3_stmt* stmt = 0;
try {
sqlite3_prepare_v2(db, sql.c_str(), &stmt);
} catch (...) {}
return stmt;
}
};
Что касается исходного вопроса, я бы сказал, что ответ "не совсем". Надлежащее уважение к DRY скажет вам взять эти длинные блоки try/catch/finally и преобразовать их в отдельные классы, которые скрывают части try/catch от остальной части, где они могут (в случае масштаба (сбоя)), и делают управление ресурсами прозрачно (в случае области действия (выход)).