Does C++ support 'finally' blocks? (And wh

2019-01-01 01:24发布

Does C++ support 'finally' blocks?

What is the RAII idiom?

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

16条回答
看风景的人
2楼-- · 2019-01-01 02:06

In C++ the finally is NOT required because of 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.

Also IMO the code looks neater (see below).

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.

C++ Like 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.

Java Like Finally

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.

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

查看更多
忆尘夕之涩
3楼-- · 2019-01-01 02:06

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.

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.

查看更多
萌妹纸的霸气范
4楼-- · 2019-01-01 02:06

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

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:

// 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:

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.

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

PS

If you need a finally block in your code, then scoped guards or ON_FINALLY/ON_EXCEPTION macros will probably better fit your needs.

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

    ...
查看更多
旧人旧事旧时光
5楼-- · 2019-01-01 02:12

I have a use case where I think finally should be a perfectly acceptable part of the C++11 language, as I think it is easier to read from a flow point of view. My use case is a consumer/producer chain of threads, where a sentinel nullptr is sent at the end of the run to shut down all threads.

If C++ supported it, you would want your code to look like this:

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

I think this is more logical that putting your finally declaration at the start of the loop, since it occurs after the loop has exited... but that is wishful thinking because we can't do it in C++. Note that the queue downstream is connected to another thread, so you can't put in the sentinel push(nullptr) in the destructor of downstream because it can't be destroyed at this point... it needs to stay alive until the other thread receives the nullptr.

So here is how to use a RAII class with lambda to do the same:

    class Finally
    {
    public:

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

and here is how you use it:

    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);
        }
    }
查看更多
旧时光的记忆
6楼-- · 2019-01-01 02:12

I also think that RIIA is not a fully useful replacement for exception handling and having a finally. BTW, I also think RIIA is a bad name all around. I call these types of classes 'janitors' and use them a LOT. 95% of the time they are neither initializing nor acquiring resources, they are applying some change on a scoped basis, or taking something already set up and making sure it's destroyed. This being the official pattern name obsessed internet I get abused for even suggesting my name might be better.

I just don't think it's reasonable to require that that every complicated setup of some ad hoc list of things has to have a class written to contain it in order to avoid complications when cleaning it all back up in the face of needing to catch multiple exception types if something goes wrong in the process. This would lead to lots of ad hoc classes that just wouldn't be necessary otherwise.

Yes it's fine for classes that are designed to manage a particular resource, or generic ones that are designed to handle a set of similar resources. But, even if all of the things involved have such wrappers, the coordination of cleanup may not just be a simple in reverse order invocation of destructors.

I think it makes perfect sense for C++ to have a finally. I mean, jeez, so many bits and bobs have been glued onto it over the last decades that it seems odd folks would suddenly become conservative over something like finally which could be quite useful and probably nothing near as complicated as some other things that have been added (though that's just a guess on my part.)

查看更多
何处买醉
7楼-- · 2019-01-01 02:13

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

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

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. Then the lack of "finally" really bites.

查看更多
登录 后发表回答