C++ 是否支持“finally”块?(我一直听说的这个“RAII”是什么?

Does C++ support 'finally' blocks? (And what's this 'RAII' I keep hearing about?)

提问人:Kevin 提问时间:10/2/2008 最后编辑:MottiKevin 更新时间:11/6/2020 访问量:306535

问:

C++ 是否支持“finally”块?

什么是RAII成语

C++ 的 RAII 习语和 C# 的“using”语句有什么区别?

C 异常 raii 最后 c++-faq

评论

1赞 18446744073709551615 11/16/2015
另请参阅以下位置的答案:stackoverflow.com/questions/7779652/...

答:

325赞 Kevin 10/2/2008 #1

不,C++ 不支持“finally”块。原因是C++反而支持RAII:“资源获取就是初始化”——一个糟糕的名字对于一个非常有用的概念。

这个想法是对象的析构函数负责释放资源。当对象具有自动存储持续时间时,当创建该对象的块退出时,将调用该对象的析构函数,即使该块在存在异常的情况下退出也是如此。以下是 Bjarne Stroustrup 对该主题的解释。

RAII 的一个常见用途是锁定互斥锁:

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

RAII 还简化了将对象用作其他类的成员的过程。当所属类被解构时,将释放由 RAII 类管理的资源,因为 RAII 托管类的析构函数将因此被调用。这意味着,当您将 RAII 用于管理资源的类中的所有成员时,您可以为所有者类使用非常简单的(甚至是默认的)析构函数,因为它不需要手动管理其成员资源生存期。(感谢 Mike B 指出这一点。

对于熟悉 C# 或 VB.NET 的用户,您可能会认识到 RAII 类似于使用 IDisposable 和 'using' 语句的 .NET 确定性销毁。事实上,这两种方法非常相似。主要区别在于 RAII 将确定性地释放任何类型的资源——包括内存。在 .NET(甚至是 .NET 语言 C++/CLI)中实现 IDisposable 时,将确定性地释放资源,但内存除外。在 .NET 中,内存不会确定性释放;内存仅在垃圾回收周期中释放。

 

† 有些人认为“破坏就是资源放弃”是 RAII 成语的更准确名称。

评论

23赞 Kevin 10/2/2008
RAII被困住了 - 真的没有改变它。试图这样做是愚蠢的。但是,您不得不承认,“资源获取即初始化”仍然是一个相当糟糕的名字。
182赞 Johannes Schaub - litb 11/25/2008
SBRM == 范围限制资源管理
64赞 Warren P 12/28/2012
当您需要清理与任何 C++ 对象的生存期不匹配的内容时,这会让您陷入困境。我猜你最终会得到 Lifetime Equals C++ Class Liftime Or Else It Gets Ugly (LECCLEOEIGU?)。
13赞 Warren P 3/21/2014
@Jasper:这就是为什么每个代码库中都有这么多几乎完全无用的类。因为 C++ 没有其他选择。这就是所谓的语言疣。
8赞 Jasper 3/21/2014
@WarrenP 不,这叫做单一责任原则。
6赞 SmacL 10/2/2008 #2

FWIW,Microsoft Visual C++ 确实支持 try,finally,它历来在 MFC 应用程序中用作捕获严重异常的方法,否则会导致崩溃。例如;

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

我过去曾用它来做一些事情,比如在退出之前保存打开文件的备份。但是,某些 JIT 调试设置会破坏此机制。

评论

5赞 gbjbaanb 10/5/2008
请记住,这不是真正的 C++ 例外,而是 SEH 例外。您可以在 MS C++ 代码中使用两者。SEH 是一个操作系统异常处理程序,它是 VB、.NET 实现异常的方式。
0赞 gbjbaanb 10/5/2008
可以使用 SetUnhandledExceptionHandler 为 SEH 异常创建“全局”未捕获的异常处理程序。
3赞 paulm 6/24/2013
SEH 很可怕,并且还会阻止调用 C++ 析构函数
35赞 Michael Burr 10/2/2008 #3

除了使用基于堆栈的对象使清理变得容易之外,RAII 也很有用,因为当对象是另一个类的成员时,也会发生相同的“自动”清理。当所属类被销毁时,RAII 类管理的资源将被清理,因为该类的 dtor 因此被调用。

这意味着,当您达到 RAII 涅槃并且类中的所有成员都使用 RAII(如智能指针)时,您可以为所有者类使用非常简单(甚至可能是默认的)dtor,因为它不需要手动管理其成员资源生存期。

评论

0赞 Kevin 10/3/2008
这是一个很好的观点。+1 给你。不过,没有多少人投票支持你。我希望你不介意我编辑了我的帖子以包含你的评论。(我当然给了你荣誉。谢谢!:)
83赞 Martin York 10/2/2008 #4

在 C++ 中,由于 RAII,不需要 finally。

RAII 将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为您只需要获得一次异常安全性(在设计/实现中)。通过使用 finally,您需要在每次使用对象时获得正确的异常安全性。

此外,IMO 代码看起来更整洁(见下文)。

例:

数据库对象。要确保使用数据库连接,必须打开和关闭它。通过使用 RAII,这可以在构造函数/析构函数中完成。

类似 RAII 的 C++

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

使用 RAII 使得正确使用 DB 对象变得非常容易。无论我们如何尝试和滥用它,DB 对象都会通过使用析构函数正确地关闭它。

Java Like 终于

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

最后使用时,对象的正确使用被委托给对象的用户。对象用户有责任正确地显式关闭数据库连接。现在,您可以争辩说,这可以在终结器中完成,但资源可能具有有限的可用性或其他约束,因此您通常希望控制对象的释放,而不是依赖于垃圾回收器的非确定性行为。

这也是一个简单的例子。
当您有多个资源需要释放时,代码可能会变得复杂。

更详细的分析可以在这里找到: http://accu.org/index.php/journals/236

评论

19赞 Cemafor 5/24/2013
// Make sure not to throw exception if one is already propagating.由于这个原因,C++ 析构函数也不要抛出异常,这一点很重要。
10赞 Martin York 5/24/2013
@Cemafor:C++不从析构函数中抛出异常的原因与 Java 不同。在 Java 中,它会起作用(你只是失去了原来的异常)。在 C++ 中,它真的很糟糕。但是 C++ 的重点是,当他编写析构函数时,您只需要执行一次(由类的设计者执行)。在 Java 中,您必须在使用时执行此操作。因此,类的用户有责任非常花时间编写相同的样板。
2赞 Martin York 11/10/2014
@Trinidad:这并不像你想象的那么简单(因为你所有的建议似乎都选择了最糟糕的选择)。这就是为什么一个问题可能比评论更适合探索这个问题。
2赞 ceztko 3/11/2019
批评“由于 RAII 而不需要”:在很多情况下,添加临时 RAII 会是太多的样板代码,而 try-finally 将非常合适。
2赞 ceztko 3/11/2019
@MartinYork我并不是说应该始终使用 try-finally 而不是 RAII。你是对的,API 应该为其结构提供适当的析构函数,或者在适当的情况下提供 RAII 保护(例如处理事务)。我想说的是,在最终用户代码中,在某些情况下,添加 RAII 并不是他可能想要使用的最合理的工具,并且 try-finally 可以在不为 RAII 创建无聊的临时结构的情况下完成这项工作。简而言之:两者都很好,明智的程序员会在每种情况下使用最合适的。
-2赞 Unhandled exception 4/24/2010 #5
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

评论

37赞 Edward Kmett 4/24/2010
可爱的成语,但它并不完全相同。在 try 块或 catch 中返回不会通过您的“finally:”代码。
11赞 Mark Lakata 12/4/2012
保留这个错误的答案是值得的(评分为 0),因为爱德华·克梅特 (Edward Kmett) 提出了一个非常重要的区别。
13赞 Ben Voigt 4/28/2013
更大的缺陷(IMO):此代码吞噬了所有例外,但事实并非如此。finally
7赞 Mephane 6/2/2010 #6

很抱歉挖出这么旧的线程,但以下推理存在重大错误:

RAII 将异常安全的责任从对象的用户转移到对象的设计者(和实现者)。我认为这是正确的地方,因为您只需要获得一次异常安全性(在设计/实现中)。通过使用 finally,您需要在每次使用对象时获得正确的异常安全性。

通常情况下,您必须处理动态分配的对象、动态数量的对象等。在 try-block 中,某些代码可能会创建许多对象(在运行时确定多少个对象),并将指向它们的指针存储在列表中。现在,这不是一个异国情调的场景,但很常见。在这种情况下,你会想写这样的东西

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

当然,当超出范围时,列表本身将被销毁,但这不会清理您创建的临时对象。

相反,你必须走丑陋的路线:

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

另外:为什么即使是托管语言也提供最终块,尽管资源无论如何都会由垃圾回收器自动释放?

提示:你可以用“finally”做更多的事情,而不仅仅是内存释放。

评论

20赞 Myto 6/2/2010
托管语言之所以需要 finally-blocks,正是因为只有一种资源是自动管理的:内存。RAII 意味着所有资源都可以以相同的方式处理,因此无需 finally。如果你在示例中实际使用了 RAII(通过在列表中使用智能指针而不是裸指针),则代码将比“finally”示例更简单。如果你不检查 new 的返回值,那就更简单了——检查它几乎毫无意义。
9赞 Hasturkun 6/2/2010
new不返回 NULL,而是抛出异常
7赞 j_random_hacker 6/4/2010
你提出了一个重要的问题,但它确实有 2 个可能的答案。一个是 Myto 给出的 -- 使用智能指针进行所有动态分配。另一种是使用标准容器,这些容器总是在销毁时销毁其内容物。无论哪种方式,每个分配的对象最终都由一个静态分配的对象拥有,该对象在销毁时自动释放它。很遗憾,由于普通指针和数组的高可见性,程序员很难发现这些更好的解决方案。
6赞 u0b34a0f6ae 10/20/2011
C++11 对此进行了改进,并直接包含在 stdlib 中。std::shared_ptrstd::unique_ptr
19赞 Ben Voigt 4/28/2013
你的例子之所以看起来如此可怕,不是因为 RAII 有缺陷,而是因为你没有使用它。原始指针不是 RAII。
36赞 Philip Couling 9/21/2010 #7

为什么即使是托管语言也提供了一个 finally 块,尽管资源无论如何都会由垃圾回收器自动释放?

实际上,基于垃圾收集器的语言“最终”需要更多。垃圾回收器不会及时销毁您的对象,因此不能依靠它来正确清理与内存无关的问题。

就动态分配的数据而言,许多人会争辩说您应该使用智能指针。

然而。。。

RAII 将异常安全的责任从对象的用户转移到设计者

可悲的是,这是它自己的垮台。旧的 C 编程习惯很难改变。当您使用用 C 或非常 C 风格编写的库时,不会使用 RAII。除了重写整个 API 前端之外,这正是您必须使用的。然后缺乏“最后”真的很咬人。

评论

15赞 James Johnston 10/4/2011
完全。。。从理想的角度来看,RAII 似乎不错。但是我必须一直使用传统的 C API(比如 Win32 API 中的 C 样式函数......获取返回某种 HANDLE 的资源是很常见的,然后需要一些函数(如 CloseHandle(HANDLE) )来清理。使用 try ...最后是处理可能的异常的好方法。(值得庆幸的是,看起来带有自定义删除程序和 C++11 lambda 的shared_ptr应该提供一些基于 RAII 的救济,不需要编写整个类来包装我只在一个地方使用的一些 API。
8赞 Mark Ransom 1/4/2012
@JamesJohnston,编写一个包含任何类型的句柄并提供 RAII 机制的包装类非常容易。例如,ATL 提供了一堆这样的工具。看来你认为这太麻烦了,但我不同意,它们很小,很容易写。
5赞 Philip Couling 1/4/2012
简单是,小不是。大小取决于您正在使用的库的复杂性。
1赞 supercat 12/1/2012
@MarkRansom:如果清理期间发生异常而另一个异常处于待处理状态,是否有任何机制可以让 RAII 做一些智能的事情?在具有 try/finally 的系统中,可以(尽管很笨拙)安排事情,以便将挂起的异常和清理期间发生的异常都存储在一个新的 .有没有合理的方法可以使用 RAII 实现这样的结果?CleanupFailedException
3赞 supercat 12/4/2012
@couling:在许多情况下,程序将调用一个方法,并想知道它是否 (1) 成功,(2) 失败而没有副作用,(3) 失败,调用者准备应对的副作用,或者 (4) 失败,调用者无法应对的副作用。只有呼叫者会知道它可以应对和不能应对的情况;呼叫者需要的是一种了解情况的方法。太糟糕了,没有标准机制来提供有关异常的最重要信息。SomeObject.DoSomething()
3赞 bcmpinc 5/31/2014 #8

不是真的,但你可以在一定程度上模仿它们,例如:

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

请注意,finally-block 本身可能会在重新抛出原始异常之前抛出异常,从而丢弃原始异常。这与 Java finally-block 中的行为完全相同。此外,您不能在 try&catch 块中使用。return

评论

4赞 sethobrien 3/24/2015
我很高兴你提到最后的块可能会抛出;这是大多数“使用 RAII”答案似乎忽略的事情。为了避免将 finally 块写入两次,您可以执行类似操作std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
1赞 VinGarcia 7/21/2016
这就是我想知道的!为什么没有其他答案解释接球(...)+空投;几乎像最终块一样工作?有时你只是需要它。
0赞 Fabio A. 8/1/2016
我在答案 (stackoverflow.com/a/38701485/566849) 中提供的解决方案应该允许从块内部抛出异常。finally
1赞 Mark Lakata 7/10/2014 #9

正如许多人所说,解决方案是使用 C++11 功能来避免最终阻塞。其中一个特点是unique_ptr

这是 Mephane 使用 RAII 模式编写的答案。

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

此处详细介绍了如何将 unique_ptr 与 C++ 标准库容器配合使用

81赞 Paolo.Bolzoni 8/27/2014 #10

RAII 通常更好,但您可以轻松地在 C++ 中获得最终语义。使用少量代码。

此外,C++ 核心指南最后给出了。

以下是指向 GSL Microsoft 实现的链接和指向 Martin Moene 实现的链接

Bjarne Stroustrup多次表示,GSL中的所有内容最终都意味着要进入标准。因此,它应该是一种面向未来的方式。

不过,如果您愿意,您可以轻松实现自己,请继续阅读。

在 C++11 中,RAII 和 lambda 允许最终制作一个通用:

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

使用示例:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

输出将是:

doing something...
leaving the block, deleting a!

就我个人而言,我使用过几次来确保在 C++ 程序中关闭 POSIX 文件描述符。

拥有一个管理资源并避免任何类型的泄漏的真正类通常更好,但这最终在创建类听起来像是矫枉过正的情况下很有用。

此外,我最终比其他语言更喜欢它,因为如果自然使用,您可以在开始代码附近编写关闭代码(在我的示例中是 newdelete),并且销毁遵循 C++ 中通常的 LIFO 顺序构造。唯一的缺点是你得到一个你并不真正使用的自动变量,并且 lambda 语法使它有点嘈杂(在我的示例中,在第四行中,只有单词 finally 和右边的 {} 块是有意义的,其余的基本上是噪音)。

另一个例子:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

如果仅在失败时才需要调用 finally,则 disable 成员很有用。例如,您必须在三个不同的容器中复制一个对象,您可以设置 finally 以撤消每个复制并在所有复制成功后禁用。这样做,如果破坏不能抛出,你就确保了强有力的保证。

禁用示例:

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

如果你不能使用 C++11,你仍然可以使用 finally,但代码变得有点冗长。只需定义一个只有构造函数和析构函数的结构,构造函数引用任何需要的东西,析构函数执行您需要的操作。这基本上就是 lambda 所做的,手动完成。

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

希望您可以使用 C++11,这段代码更多的是为了展示自 C++ 的最初几周以来“C++ 最终不支持”一直是无稽之谈,甚至在 C++ 得名之前就有可能编写这种代码。

评论

0赞 user1633272 12/1/2016
可能存在一个问题:在函数“finally(F f)”中,它返回 FinalAction 的对象,因此可以在返回函数 finally 之前调用解构函数。也许我们应该使用 std::function 而不是模板 F。
1赞 anderas 5/5/2017
请注意,这与流行的成语基本相同,只是名称不同。FinalActionScopeGuard
1赞 Nulano 8/10/2017
这种优化安全吗?
2赞 Nulano 9/24/2017
@Paolo.博尔佐尼 很抱歉没有早点回复,我没有收到您的评论通知。我担心 finally 块(我在其中调用 DLL 函数)会在作用域结束之前调用(因为该变量未使用),但后来在 SO 上发现了一个问题,消除了我的担忧。我会链接到它,但不幸的是,我再也找不到它了。
1赞 user2445507 3/16/2019
disable() 函数是你原本干净的设计上的一个疣。如果你希望finally只在失败的情况下被调用,那么为什么不直接使用catch语句呢?这不就是它的用途吗?
0赞 jave.web 9/2/2015 #11

编辑

如果您不中断/继续/返回等,您可以向任何未知异常添加一个 catch,并将 always 代码放在它后面。这也是您不需要重新抛出异常的时候。

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

那么问题出在哪里呢?

通常,在其他编程语言中,通常无论如何都会运行(通常意味着无论任何返回、中断、继续......除了某种系统 - 每种编程语言差异很大 - 例如.PHP和Java在那一刻退出,但Python最终还是执行然后退出。exit()

但是我上面描述的代码不能以这种方式
工作=>以下代码输出:
something wrong!

#include <stdio.h>
#include <iostream>
#include <string>

std::string test() {
    try{
       // something that might throw exception
       throw "exceptiooon!";

       return "fine";
    } catch( ... ){
       return "something wrong!";
    }
    
    return "finally";
}

int main(void) {
    
    std::cout << test();
    
    
    return 0;
}

评论

0赞 burlyearly 12/21/2016
这是行不通的,因为 finally 块的全部意义在于执行清理,即使代码应该允许异常离开代码块。考虑: ' try { // 可能抛出 “B” 的东西 } catch (A & a) { } 最后 { // 如果 C++ 有它... // 必须发生的事情,即使抛出 “B” 也不会执行。 } // 如果抛出 “B” 不会执行。'恕我直言,例外的重点是减少错误处理代码,因此无论在哪里可能发生抛出,捕获块都会适得其反。这就是 RAII 有帮助的原因:如果自由应用,例外在顶层和底层最为重要。
1赞 jave.web 12/22/2016
@burlyearly尽管你的观点不是神圣的,但我明白了,但C++不是这样的事情,所以你必须将其视为模仿这种行为的顶层。
5赞 Mark Lakata 12/1/2015 #12

我有一个用例,我认为应该是 C++11 语言中完全可以接受的一部分,因为我认为从流程的角度来看它更容易阅读。我的用例是线程的消费者/生产者链,在运行结束时发送哨兵以关闭所有线程。finallynullptr

如果 C++ 支持它,你会希望你的代码看起来像这样:

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

我认为这比将你的 finally 声明放在循环的开头更合乎逻辑,因为它发生在循环退出之后......但这是一厢情愿的想法,因为我们不能在 C++ 中做到这一点。请注意,队列连接到另一个线程,因此您不能将哨兵放入析构函数中,因为此时无法销毁它...它需要保持活动状态,直到另一个线程收到 .downstreampush(nullptr)downstreamnullptr

因此,这里是如何将 RAII 类与 lambda 一起使用来执行相同的操作:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

以下是您如何使用它:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

评论

0赞 Fabio A. 8/1/2016
嗨,我相信我上面的回答(stackoverflow.com/a/38701485/566849)完全满足您的要求。
3赞 Fabio A. 8/1/2016 #13

我想出了一个宏,它几乎可以像 Java 中的关键字一样使用¹;它利用了 和 friends、lambda 函数和 ,所以它需要或更高;它还使用了复合语句表达式 GCC 扩展,CLANG 也支持该扩展。finallyfinallystd::exception_ptrstd::promiseC++11

警告:此答案的早期版本使用了该概念的不同实现,但具有更多限制。

首先,让我们定义一个帮助程序类。

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

然后是实际的宏。

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

它可以像这样使用:

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

使用 使它非常容易实现,但它也可能引入相当多的不必要的开销,而这些开销可以通过仅重新实现所需的功能来避免。std::promisestd::promise


¹ 注意:有些东西不像 java 版本的 .在我的头顶上:finally

  1. 不可能在 和 的块中使用语句从外部循环中断,因为它们位于 lambda 函数中;breaktrycatch()
  2. 之后必须至少有一个块:这是 C++ 要求;catch()try
  3. 如果函数的返回值不是 void,但在 and 块中没有返回值,则编译将失败,因为宏将扩展为要返回 .这可能是,呃,由于拥有某种宏而产生的空白trycatch()'sfinallyvoidfinally_noreturn

总而言之,我不知道我自己是否会使用这些东西,但玩它很有趣。:)

评论

0赞 Fabio A. 8/2/2016
@MarkLakata,我更新了帖子,提供了更好的实现,支持抛出异常和返回。
0赞 Mark Lakata 8/2/2016
看起来不错。您可以通过在宏的开头放置一个不可能的块来摆脱警告 2,其中 xxx 是一个虚假类型,只是为了至少有一个 catch 块。catch(xxx) {}finally
0赞 Fabio A. 8/2/2016
@MarkLakata,我也想到了这一点,但那样就无法使用了,不是吗?catch(...)
0赞 Mark Lakata 8/2/2016
我不这么认为。只需在永远不会使用的私有命名空间中编造一个晦涩难懂的类型即可。xxx
0赞 Fabio A. 8/2/2016
Gcc吐出.Clang 也这样做。我相信标准只是禁止在中间有一个块。finally.cpp:60:5: error: ‘...’ handler must be the last handler for its try blockcatch(...)
11赞 anton_rh 11/30/2017 #14

另一个使用 C++11 lambda 函数的“finally”块仿真

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

让我们希望编译器能够优化上面的代码。

现在我们可以编写这样的代码:

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

如果你愿意,你可以把这个成语包装成“try - finally”宏:

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

现在 “finally” 块在 C++11 中可用:

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

就我个人而言,我不喜欢“finally”成语的“宏”版本,并且更喜欢使用纯“with_finally”函数,即使在这种情况下语法更笨重。

您可以在此处测试上面的代码:http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

聚苯乙烯

如果你在代码中需要一个 finally 块,那么作用域保护ON_FINALLY/ON_EXCEPTION 宏可能更适合你的需求。

以下是 ON_FINALLY/ON_EXCEPTION 用法的简短示例:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...
6赞 tobi_s 8/8/2018 #15

正如其他答案中指出的,C++可以支持类似的功能。此功能的实现可能最接近标准语言的一部分,是 C++ Core Guidelines 附带的实现,这是一组使用 C++ 的最佳实践,由 Bjarne Stoustrup 和 Herb Sutter 编辑。finally 的实现指南支持库 (GSL) 的一部分。在整个准则中,建议在处理旧式接口时使用 of,并且它也有自己的准则,标题为“如果没有合适的资源句柄可用,则使用final_action对象来表示清理”。finallyfinally

因此,不仅 C++ 支持 ,实际上建议在许多常见用例中使用它。finally

GSL 实现的示例使用如下所示:

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

GSL 的实现和用法与 Paolo.Bolzoni 的回答非常相似。一个区别是 创建的对象缺少调用。如果你需要这个功能(比如说,在资源组装好后返回资源,并且不会发生异常),你可能更喜欢 Paolo 的实现。否则,使用 GSL 与使用标准化功能一样接近。gsl::finally()disable()

1赞 Dean Roddey 12/19/2018 #16

我还认为 RIIA 并不是异常处理和 finally 的完全有用的替代品。顺便说一句,我也认为RIIA是一个坏名字。我称这些类型的课程为“看门人”,并经常使用它们。95% 的时间,他们既不是初始化也不是获取资源,而是在范围的基础上应用一些更改,或者采用已经设置的东西并确保它被销毁。这是官方模式名称痴迷的互联网,我甚至因为建议我的名字可能更好而受到滥用。

我只是不认为要求某些临时事物列表的每个复杂设置都必须编写一个类来包含它是不合理的,以避免在清理所有备份时出现复杂情况,因为如果在此过程中出现问题,则需要捕获多个异常类型。这将导致许多临时类,否则这些类就没有必要了。

是的,对于旨在管理特定资源的类或旨在处理一组类似资源的泛型类来说,这很好。但是,即使所有涉及的事情都有这样的包装器,清理的协调也可能不仅仅是简单的反序调用析构函数。

我认为 C++ 有一个 finally 是完全有意义的。我的意思是,天哪,在过去的几十年里,有太多的零碎的东西被粘在上面,以至于似乎奇怪的人们会突然对像 finally 这样的东西变得保守,这可能非常有用,而且可能没有像其他一些添加的东西那么复杂(尽管这只是我的猜测。