try / catch / finalを持つ言語を使用する場合、Dのfailure / success / exitスコープステートメントは引き続き役立ちますか?Dは、これらのステートメントがDで使用される理由を説明する可能性のある最終的なものではないようです。しかし、C#のような言語では、それは役に立ちますか?私は言語を設計しているので、多くのプロを見つけたらそれを追加します。
6 に答える
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);
}
コードはスパゲッティに変わり、エラーリカバリがショップ全体に広がり、すべてのtryブロックに一定レベルのインデントが強制されます。私の意見では、scope(X)を使用したバージョンは、はるかに読みやすく、理解しやすいものです。
try / catch/finallyはあるレベルのネストを強制します。スコープガードはしません。さらに、割り当てコードと同じ「領域」にクリーンアップコードを記述できるため、「ファイルを開き、関数の最後までスクロールし、ファイルを閉じ、関数の先頭までスクロールする」必要がなくなります。
ただし、基本的には、try / catch/finally例外処理のより便利な表現です。try/catch/ finallyで実行できることはすべて、スコープガードで実行でき、その逆も可能です。
その価値はありますか?私はDファンボーイです(つまり、偏見があります)が、間違いなく言うでしょう。
免責事項私も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();
}
失敗-終了と成功-終了を区別することは、非常に便利な場合があります-私はDの実際の経験がありませんが、Pythonのwith
ステートメントでもそれが可能であり、たとえば、DBをコミットまたはロールバックするのに非常に便利です。本体の保護された部分で開かれたトランザクション。
この当時の新しいPython機能(しばらく前からあります;-)をC ++とJavaの達人である友人や同僚に説明したとき、彼らはすぐに理解し、そのような機能を持つことに興味を持ったことがわかりました(Pythonにはfinally
、もですが、他の言語[またはC ++の「ブロック内の自動変数のRAII破棄」に相当するもの]のように、成功と失敗を区別するのに役立ちません。
スコープ(exit)、scope(failure)、scope(success)もC++で使用できることに注意してください。
- scope(exit)には、Boost.ScopeExitライブラリがあります。
- scope(failure)とscope(success)には、stack_unwindingライブラリがあります。
次の構文がサポートされています、ケース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
@DK、C ++(およびJavaだと思います)では、「匿名」クラスを使用して、scope(exit)と同じことを簡単に実行できることを指摘しておく必要があります。
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");
...
}
また、これを2回以上行った場合は、すぐに完全なクラスに変換して、RAIIを処理します。書くのはとても簡単で、CSqlite_DBやCSqlite_Stmtのようなクラスを作成せずにsqlite(例で使用されている)を使用するC++プログラムを想像することはできません。実際、演算子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 / finalの長いブロックを取得し、それらを別のクラスに変換して、try / catchの部分を他の部分から隠して(スコープ(失敗)の場合)、リソース管理は透過的です(scope(exit)の場合)。