【问题标题】:Is D's scope failure/success/exit necessary?D 的作用域失败/成功/退出是否必要?
【发布时间】:2010-11-17 21:07:56
【问题描述】:

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

【问题讨论】:

  • 您有博客或任何类型的网站吗?
  • Ctrl Alt D-1337:不。你应该给我发消息(我的电子邮件在我的个人资料中)。我可能会以不同的名称发布它。 (我有不同的用户名用于不同的兴趣)
  • BCS:哦。很高兴知道。几个月前,我从前到后阅读了手册。我想这让我忘记了。

标签: c# c++ d


【解决方案1】:

scope(X) 不是必需的,就像 for 不是必需的一样,只要您有 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);
}

代码已变成spaghetti,将错误恢复传播到整个商店,并为每个尝试块强制缩进。在我看来,使用 scope(X) 的版本更具可读性和更容易理解。

【讨论】:

  • Gold,不仅因为你应该告诉我它是如何变成意大利面条的,而且还因为代码的功能如此具体以至于析构函数应该/不能成为解决方案。
  • 第一个代码示例非常好。这是一股清新的空气。我们都习惯于阅读第二种翻译形式,第一种看起来很奇怪,但它具有与“提前返回”或“提前抛出”代码相同的信心和进展:考虑问题 A,陈述解决方案,忘记 A然后继续考虑问题 B,陈述解决方案,忘记 B 并继续前进。
【解决方案2】:

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

但从根本上说,它只是 try/catch/finally 异常处理的一种更方便的表达方式 - 任何事情你可以用 try/catch/finally 做,也可以用作用域保护做,反之亦然。 p>

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

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

      【讨论】:

      • 我不明白。我读了这个effbot.org/zone/python-with-statement.htm 看起来它允许ctor 和dtor 运行。没有语句就不能运行吗?我不太了解 python 或实际发生了什么
      • 要点是,上下文管理器的__exit__ 特殊方法被调用,其中包含有关正在传播的异常的信息(如果有);因此,例如,用于 DB 操作的上下文管理器可以区分成功案例(保证 DB 提交)和失败案例(需要 DB 回滚)。你在谈论一个完全不同的问题——dtors 本身可以在 try/finally 中运行,这在语法上比 RAII 更笨拙(看看!-)但在功能上是等效的。但在某些情况下,区分成功与失败是至关重要的!
      【解决方案5】:

      值得一提的是,C++ 也可以使用 scope(exit)、scope(failure) 和 scope(success)。

      支持以下语法,案例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
      

      【讨论】:

      • wtf 这么多代码!?我在 6 行 C++11 代码中实现了 SCOPE_EXIT、SCOPE_SUCCESS 和 SCOPE_FAIL。我所做的只是传入一个 lambda 函数。但无论如何+1。我不可能包括那些标题。非常矫枉过正。继承人 3 行,其他 3 行是特定于我的代码的 marco defs pastebin.com/zaLZ6fP7
      • @acidzombie24: 1. 你的解决方案不能正常工作,检查这个:ideone.com/IcWMEf ~Test()里面没有任何异常,但是打印了“failure”。
      • @acidzombie24: 2. 对于仅限 C++11 的解决方案确实更短。但是,该库也适用于 C++98/C++03。大多数代码与 C++98/C++03 的“lambdas”模拟有关。
      • @acidzombie24: 3. 熟悉 Boost.ScopeExit 库 - boost.org/doc/libs/1_51_0/libs/scope_exit/doc/html/index.html 请注意,它适用于 C++98/03/11。检查它的代码:boost.org/doc/libs/1_51_0/boost/scope_exit.hpp
      • 不错的收获!我没有将它用于生产,但如果我正在编写特定于范围的代码,我会使用它:/
      【解决方案6】:

      @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 部分隐藏在它们可以的其他部分之外(在范围(失败)的情况下)并使资源管理透明(在范围(退出)的情况​​下)。

      【讨论】:

      • ...虽然我会指出,范围(退出)比任何一个版本都少了大约 200 个字符:-)
      • 其实,自从写了这篇我更喜欢D的方法。当我真正考虑范围(失败)和范围(成功)时,我被卖掉了。如果没有丑陋的 MACRO 支持,RAII 方式就无法复制这些功能。尽管如此,对于 SqlLite 示例,完整的 C++ 包装类是“正确的”C++ 解决方案。
      猜你喜欢
      • 1970-01-01
      • 2019-04-04
      • 2016-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-28
      • 2022-01-17
      • 2016-10-08
      相关资源
      最近更新 更多