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

前端 未结 16 1421
情深已故
情深已故 2020-11-22 17:15

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条回答
  • 2020-11-22 17:22

    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.

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

    An example use of the GSL implementation would look like:

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

    0 讨论(0)
  • 2020-11-22 17:24

    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);
            }
        }
    
    0 讨论(0)
  • 2020-11-22 17:24
    try
    {
      ...
      goto finally;
    }
    catch(...)
    {
      ...
      goto finally;
    }
    finally:
    {
      ...
    }
    
    0 讨论(0)
  • 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.)

    0 讨论(0)
  • 2020-11-22 17:30

    As many people have stated, the solution is to use C++11 features to avoid finally blocks. One of the features is unique_ptr.

    Here is Mephane's answer written using RAII patterns.

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

    Some more introduction to using unique_ptr with C++ Standard Library containers is here

    0 讨论(0)
  • 2020-11-22 17:33

    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.

    0 讨论(0)
提交回复
热议问题