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就相当于使用标准化的特性。

其他回答

另一个“finally”块模拟使用c++ 11 lambda函数

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

PS

如果你的代码中需要一个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(); });

    ...

RAII通常更好,但在c++中可以很容易地获得finally语义。使用少量的代码。

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

这里有一个到GSL微软实现的链接和一个到Martin Moene实现的链接

Bjarne Stroustrup多次表示,GSL中的所有内容最终都将被纳入标准。所以它最终应该是一种经得起考验的使用方式。

如果你想,你可以很容易地实现自己,继续阅读。

在c++ 11中RAII和lambdas允许做出一般的最后:

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文件描述符。

有一个真正的类来管理资源,从而避免任何类型的泄漏通常是更好的,但这最终是有用的情况下,使一个类听起来有点多余。

此外,我喜欢它胜过其他语言,因为如果自然地使用它,你可以在开始代码附近编写结束代码(在我的例子中是new和delete),并且在c++中按照后进先出(LIFO)的顺序进行构造。唯一的缺点是你得到了一个你并不真正使用的auto变量,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,你仍然可以使用,但是代码会变得有点冗长。只需定义一个只有构造函数和析构函数的结构,构造函数引用所需的任何内容,而析构函数执行所需的操作。这就是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++得到它的名字之前就可以编写这种代码。

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

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

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

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