D的范围是否失败/成功/退出是否必要?

当使用具有try / catch / finally的语言时,D的失败/成功/退出范围语句是否仍然有用? D似乎没有最终可以解释为什么在D中使用这些语句。但是使用像C#这样的语言是否有用? 我正在设计一种语言,所以如果我看到很多专业人士,我会加入它。

scope(X)没有必要以不必要的方式提供,只要你有ifgoto

这是我今天写的一些代码中的一个释义示例:

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

代码变成意大利面条 ,在整个商店中传播错误恢复并强制每个尝试块的缩进级别。 在我看来,使用范围(X)的版本更易读,更容易理解。

try / catch /最终强制嵌套; 范围守卫没有。 此外,它们允许您在与分配代码相同的“区域”中编写清理代码,因此不再需要“打开文件,滚动到function结束,关闭文件,滚动到function顶部”。

从根本上说,它只是一个更方便的try / catch / finallyexception处理表达式 – 你可以用try / catch /最后做任何事情,你可以用范围保护,反向。

这值得么? 我是一个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语句也允许这样做,并且我发现它非常有用,例如,提交或回滚在正文的受保护部分中打开的数据库事务。

当我解释这个新的Python特性(它现在已经存在一段时间了;-)给了C ++和Java大师的朋友和同事时,我发现他们立即理解了,并且看到了对这样一个特性的兴趣(Python确实有finally ,但这对于区分成功与失败没有任何帮助,就像在其他语言中一样[或者C ++的“RAII破坏块中的自动变量”等同于])。

值得一提的是,范围(退出),范围(失败)和范围(成功)也可用于C ++。

  • 对于范围(退出),有Boost.ScopeExit库。
  • 对于范围(失败)和范围(成功),有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"); ... } 

如果您不止一次这样做,您会立即将其变成一个完整的课程来为您处理RAII。 编写起来非常简单我无法想象使用sqlite的C ++程序(如示例中所使用的)而不创建类似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部分隐藏起来,远离其他部分(在范围(失败)的情况下)并制作资源管理透明(在范围(退出)的情况​​下)。