当使用具有 try/catch/finally 的语言时,D 的失败/成功/退出范围语句仍然有用吗?D 似乎没有 finally 这可以解释为什么在 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();
}
值得一提的是,作用域(退出)、作用域(失败)和作用域(成功)也可用于 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
案例二:
{
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
区分失败退出和成功退出在某些时候非常有用——我没有使用 D 的实际经验,但 Python 的with
语句也允许这样做,我发现它非常有用,例如,提交或回滚数据库在主体的受保护部分中打开的事务。
当我向 C++ 和 Java 专家的朋友和同事解释这个当时新的 Python 特性(它已经存在一段时间了;-)时,我发现他们立即理解了,并看到了对拥有这样一个特性的兴趣(Python 确实有finally
, 也一样,但这对于区分成功与失败没有帮助,就像在其他语言中一样[或 C++ 的“块中自动变量的 RAII 破坏”等价物])。
@DK,应该指出,在 C++(我认为是 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。编写起来非常简单,我无法想象一个 C++ 程序使用 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 部分隐藏起来,使其远离其他部分(在范围(失败)的情况下),并使资源管理透明(在范围(退出)的情况下)。