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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/161177/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:22:14  来源:igfitidea点击:

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

c++exceptionraiifinallyc++-faq

提问by Kevin

Does C++ support 'finally' blocks?

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

What is the RAII idiom?

什么是RAII 习语

What is the difference between C++'s RAII idiom and C#'s 'using' statement?

C++ 的 RAII 习惯用法和C# 的“使用”语句之间有什么区别?

回答by Kevin

No, C++ does not support 'finally' blocks. The reason is that C++ instead supports RAII: "Resource Acquisition Is Initialization" -- a poor name?for a really useful concept.

不,C++ 不支持“finally”块。原因是 C++ 支持 RAII:“Resource Acquisition Is Initialization”——一个糟糕的名字对于一个非常有用的概念。

The idea is that an object's destructor is responsible for freeing resources. When the object has automatic storage duration, the object's destructor will be called when the block in which it was created exits -- even when that block is exited in the presence of an exception. Here is Bjarne Stroustrup's explanationof the topic.

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

A common use for RAII is locking a mutex:

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 also simplifies using objects as members of other classes. When the owning class' is destructed, the resource managed by the RAII class gets released because the destructor for the RAII-managed class gets called as a result. This means that when you use RAII for all members in a class that manage resources, you can get away with using a very simple, maybe even the default, destructor for the owner class since it doesn't need to manually manage its member resource lifetimes. (Thanks to Mike Bfor pointing this out.)

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

For those familliar with C# or VB.NET, you may recognize that RAII is similar to .NET deterministic destruction using IDisposable and 'using' statements. Indeed, the two methods are very similar. The main difference is that RAII will deterministically release any type of resource -- including memory. When implementing IDisposable in .NET (even the .NET language C++/CLI), resources will be deterministically released except for memory. In .NET, memory is not deterministically released; memory is only released during garbage collection cycles.

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

 

 

? Some people believe that "Destruction is Resource Relinquishment" is a more accurate name for the RAII idiom.

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

回答by Martin York

In C++ the finally is NOTrequired because of RAII.

在C ++中总算是不是必需的,因为RAII的。

RAII moves the responsibility of exception safety from the user of the object to the designer (and implementer) of the object. I would argue this is the correct place as you then only need to get exception safety correct once (in the design/implementation). By using finally you need to get exception safety correct every time you use an object.

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

Also IMO the code looks neater (see below).

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

Example:

例子:

A database object. To make sure the DB connection is used it must be opened and closed. By using RAII this can be done in the constructor/destructor.

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

C++ Like RAII

C++ 喜欢 RAII

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.

The use of RAII makes using a DB object correctly very easy. The DB object will correctly close itself by the use of a destructor no matter how we try and abuse it.

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

Java Like Finally

Java 喜欢终于

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.
        }
    }
}

When using finally the correct use of the object is delegated to the user of the object. i.e.It is the responsibility of the object user to correctly to explicitly close the DB connection. Now you could argue that this can be done in the finaliser, but resources may have limited availability or other constraints and thus you generally do want to control the release of the object and not rely on the non deterministic behavior of the garbage collector.

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

Also this is a simple example.
When you have multiple resources that need to be released the code can get complicated.

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

A more detailed analysis can be found here: http://accu.org/index.php/journals/236

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

回答by Paolo.Bolzoni

RAII is usually better, but you can have easily the finallysemantics in C++. Using a tiny amount of code.

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

Besides, the C++ Core Guidelines give finally.

此外,C++ Core Guidelines 最后给出。

Here is a link to the GSL Microsoft implementationand a link to the Martin Moene implementation

这是GSL Microsoft 实现的链接和Martin Moene 实现的链接

Bjarne Stroustrup multiple times said that everything that is in the GSL it meant to go in the standard eventually. So it should be a future-proof way to use finally.

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

You can easily implement yourself if you want though, continue reading.

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

In C++11 RAII and lambdas allows to make a general finally:

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

example of use:

使用示例:

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

the output will be:

输出将是:

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

Personally I used this few times to ensure to close POSIX file descriptor in a C++ program.

我个人使用了几次以确保在 C++ 程序中关闭 POSIX 文件描述符。

Having a real class that manage resources and so avoids any kind of leaks is usually better, but this finallyis useful in the cases where making a class sounds like an overkill.

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

Besides, I like it better than other languages finallybecause if used naturally you write the closing code nearby the opening code (in my example the newand delete) and destruction follows construction in LIFO order as usual in C++. The only downside is that you get an auto variable you don't really use and the lambda syntax make it a little noisy (in my example in the fourth line only the word finallyand the {}-block on the right are meaningful, the rest is essentially noise).

此外,我最终比其他语言更喜欢它,因为如果自然地使用它,您可以在开始代码附近编写结束代码(在我的示例中是newdelete),并且在 C++ 中像往常一样按照 LIFO 顺序进行销毁。唯一的缺点是你得到了一个你并不真正使用的自动变量,并且 lambda 语法使它有点嘈杂(在我的第四行示例中,只有单词finally和右侧的 {}-block 是有意义的,休息本质上是噪音)。

Another example:

另一个例子:

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

The disablemember is useful if the finallyhas to be called only in case of failure. For example, you have to copy an object in three different containers, you can setup the finallyto undo each copy and disable after all copies are successful. Doing so, if the destruction cannot throw, you ensure the strong guarantee.

禁用该成员是否有用最终只有在失败的情况下被调用。例如,你必须在三个不同的容器中复制一个对象,你可以设置finally来撤消每个复制并在所有复制成功后禁用。这样做,如果破坏不能扔,你就确保了强有力的保证。

disableexample:

禁用示例:

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

If you cannot use C++11 you can still have finally, but the code becomes a bit more long winded. Just define a struct with only a constructor and destructor, the constructor take references to anything needed and the destructor does the actions you need. This is basically what the lambda does, done manually.

如果您不能使用 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"; }

回答by Michael Burr

Beyond making clean up easy with stack-based objects, RAII is also useful because the same 'automatic' clean up occurs when the object is a member of another class. When the owning class is destructed, the resource managed by the RAII class gets cleaned up because the dtor for that class gets called as a result.

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

This means that when you reach RAII nirvana and all members in a class use RAII (like smart pointers), you can get away with a very simple (maybe even default) dtor for the owner class since it doesn't need to manually manage its member resource lifetimes.

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

回答by Philip Couling

why is it that even managed languages provide a finally-block despite resources being deallocated automatically by the garbage collector anyway?

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

Actually, languages based on Garbage collectors need "finally" more. A garbage collector does not destroy your objects in a timely manner, so it can not be relied upon to clean up non-memory related issues correctly.

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

In terms of dynamically-allocated data, many would argue that you should be using smart-pointers.

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

However...

然而...

RAII moves the responsibility of exception safety from the user of the object to the designer

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

Sadly this is its own downfall. Old C programming habits die hard. When you're using a library written in C or a very C style, RAII won't have been used. Short of re-writing the entire API front-end, that's just what you have to work with. Thenthe lack of "finally" really bites.

可悲的是,这是它自己的垮台。旧的 C 编程习惯很难改掉。当您使用用 C 或非常 C 风格编写的库时,不会使用 RAII。无需重新编写整个 API 前端,这就是您必须使用的。 然后缺少“终于”真的很痛苦。

回答by anton_rh

Another "finally" block emulation using C++11 lambda functions

另一个使用 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();
}

Let's hope the compiler will optimize the code above.

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

Now we can write code like this:

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

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

If you wish you can wrap this idiom into "try - finally" macros:

如果您愿意,可以将这个习语包装成“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 :(

Now "finally" block is available in C++11:

现在“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

Personally I don't like the "macro" version of "finally" idiom and would prefer to use pure "with_finally" function even though a syntax is more bulky in that case.

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

You can test the code above here: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

你可以在这里测试上面的代码:http: //coliru.stacked-crooked.com/a/1d88f64cb27b3813

PS

聚苯乙烯

If you need a finallyblock in your code, then scoped guardsor ON_FINALLY/ON_EXCEPTIONmacros will probably better fit your needs.

如果您的代码中需要finally块,那么范围保护ON_FINALLY/ON_EXCEPTION宏可能更适合您的需求。

Here is short example of usage 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(); });

    ...

回答by Mephane

Sorry for digging up such an old thread, but there is a major error in the following reasoning:

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

RAII moves the responsibility of exception safety from the user of the object to the designer (and implementer) of the object. I would argue this is the correct place as you then only need to get exception safety correct once (in the design/implementation). By using finally you need to get exception safety correct every time you use an object.

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

More often than not, you have to deal with dynamically allocated objects, dynamic numbers of objects etc. Within the try-block, some code might create many objects (how many is determined at runtime) and store pointers to them in a list. Now, this is not an exotic scenario, but very common. In this case, you'd want to write stuff like

通常情况下,您必须处理动态分配的对象、对象的动态数量等。在 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();
    }
  }
}

Of course the list itself will be destroyed when going out of scope, but that wouldn't clean up the temporary objects you have created.

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

Instead, you have to go the ugly route:

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

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

Also: why is it that even managed lanuages provide a finally-block despite resources being deallocated automatically by the garbage collector anyway?

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

Hint: there's more you can do with "finally" than just memory deallocation.

提示:您可以使用“finally”进行更多操作,而不仅仅是内存释放。

回答by tobi_s

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 finallyis part of the Guidelines Support Library(GSL). Throughout the Guidelines, use of finallyis 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++ 可以支持finally-like 功能。此功能的实现可能最接近于标准语言的一部分,是随C++ 核心指南 一起实现的,这是一组使用 C++ 的最佳实践,由 Bjarne Stoustrup 和 Herb Sutter 编辑。的实现finally指南支持库(GSL) 的一部分。在整个指南中,finally建议在处理旧式接口时使用 of ,并且它也有自己的指南,标题为Use a final_action object to express cleanup if no合适的资源句柄可用

So, not only does C++ support finally, it is actually recommended to use it in a lot of common use-cases.

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

An example use of the GSL implementation would look like:

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.
}

The GSL implementation and usage is very similar to the one in Paolo.Bolzoni's answer. One difference is that the object created by gsl::finally()lacks the disable()call. If you need that functionality (say, to return the resource once it's assembled and no exceptions are bound to happen), you might prefer Paolo's implementation. Otherwise, using GSL is as close to using standardized features as you will get.

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

回答by SmacL

FWIW, Microsoft Visual C++ does support try,finally and it has historically been used in MFC apps as a method of catching serious exceptions that would otherwise result in a crash. For example;

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

I've used this in the past to do things like save backups of open files prior to exit. Certain JIT debugging settings will break this mechanism though.

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

回答by bcmpinc

Not really, but you can emulate them to some extend, for example:

并非如此,但您可以在一定程度上模拟它们,例如:

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;

Note that the finally-block might itself throw an exception before the original exception is re-thrown, thereby discarding the original exception. This is the exact same behavior as in a Java finally-block. Also, you cannot use returninside the try&catch blocks.

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