18

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

4

6 回答 6

39

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) 的版本更具可读性和更容易理解。

于 2009-08-08T10:04:06.283 回答
9

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

但从根本上说,它只是 try/catch/finally 异常处理的一种更方便的表达方式——你可以用 try/catch/finally 做的任何事情都可以用作用域守卫来做,然后反过来。

这值得么?我是一个 D 迷(所以,有偏见),但我肯定会说。

于 2009-08-08T02:45:30.677 回答
6

免责声明我也是一个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();
}
于 2009-08-08T04:28:17.613 回答
5

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

支持以下语法,案例 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
于 2012-10-21T00:23:40.550 回答
5

区分失败退出和成功退出在某些时候非常有用——我没有使用 D 的实际经验,但 Python 的with语句也允许这样做,我发现它非常有用,例如,提交或回滚数据库在主体的受保护部分中打开的事务。

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

于 2009-08-08T02:45:52.220 回答
2

@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 部分隐藏起来,使其远离其他部分(在范围(失败)的情况下),并使资源管理透明(在范围(退出)的情况​​下)。

于 2010-06-21T18:27:09.720 回答