c++支持“finally”块吗?

RAII习语是什么?

c++的RAII习语和c#的using语句有什么区别?


当前回答

As pointed out in the other answers, C++ can support finally-like functionality. The implementation of this functionality that is probably closest to being part of the standard language is the one accompanying the C++ Core Guidelines, a set of best practices for using C++ edited by Bjarne Stoustrup and Herb Sutter. An implementation of finally is part of the Guidelines Support Library (GSL). Throughout the Guidelines, use of finally is recommended when dealing with old-style interfaces, and it also has a guideline of its own, titled Use a final_action object to express cleanup if no suitable resource handle is available.

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

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的回答。一个区别是gsl::finally()创建的对象缺少disable()调用。如果您需要该功能(例如,在资源组装完成后返回资源,并且不会发生任何异常),那么您可能更喜欢Paolo的实现。否则,使用GSL就相当于使用标准化的特性。

其他回答

编辑

如果你不中断/继续/返回等等,你可以添加一个捕获到任何未知的异常,并把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(); 

那么问题是什么呢?

通常,在其他编程语言中,finally通常无论如何都运行(通常是指不管任何返回、中断、继续等等),除了某种系统exit()——这在每种编程语言中有很大不同——例如,PHP和Java只是在那一刻退出,但Python无论如何都执行finally,然后退出。

但是我上面描述的代码并不是这样工作的 =>下面的代码只输出一些错误!:

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

为什么即使是托管语言也会提供final块,尽管垃圾收集器会自动释放资源?

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

就动态分配数据而言,许多人认为应该使用智能指针。

然而……

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

可悲的是,这是它自己的失败。旧的C编程习惯很难改掉。当您使用用C或非常C风格编写的库时,将不会使用RAII。除了重写整个API前端,这就是你必须要处理的。那么,“终于”这个词的缺失真的很伤人。

我想出了一个finally宏,可以像¹Java中的finally关键字一样使用;它使用std::exception_ptr及其友项,lambda函数和std::promise,因此它要求c++ 11或以上;它还使用了clang也支持的复合语句表达式GCC扩展。

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

首先,让我们定义一个helper类。

#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::promise使其非常容易实现,但它可能也引入了相当多不必要的开销,这些开销可以通过只从std::promise中重新实现所需的功能来避免。


注意:有一些东西不像java版本的finally那样工作。我能想到的是:

it's not possible to break from an outer loop with the break statement from within the try and catch()'s blocks, since they live within a lambda function; there must be at least one catch() block after the try: it's a C++ requirement; if the function has a return value other than void but there's no return within the try and catch()'s blocks, compilation will fail because the finally macro will expand to code that will want to return a void. This could be, err, avoided by having a finally_noreturn macro of sorts.

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

很抱歉挖了这么老的一个线程,但下面的推理有一个重大错误:

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

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

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

另外:为什么即使是托管语言也会提供一个final块,尽管垃圾收集器会自动释放资源?

提示:使用“finally”可以做的不仅仅是内存释放。

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

如果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++中实现它。注意,下游队列连接到另一个线程,所以你不能在下游的析构函数中放入哨兵推(nullptr),因为此时它不能被销毁……它需要保持活动状态,直到另一个线程接收到nullptr。

下面是如何使用带有lambda的RAII类来做同样的事情:

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