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

前端 未结 16 1389
情深已故
情深已故 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:47

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

    Besides, the C++ Core Guidelines give finally.

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

    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.

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

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

    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.

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

    Besides, I like it better than other languages finally because if used naturally you write the closing code nearby the opening code (in my example the new and 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 finally and the {}-block on the right are meaningful, the rest is essentially noise).

    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 disable member is useful if the finally has to be called only in case of failure. For example, you have to copy an object in three different containers, you can setup the finally to undo each copy and disable after all copies are successful. Doing so, if the destruction cannot throw, you ensure the strong guarantee.

    disable example:

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

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

    Hopefully you can use C++11, this code is more to show how the "C++ does not support finally" has been nonsense since the very first weeks of C++, it was possible to write this kind of code even before C++ got its name.

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

    EDITED

    If you are not breaking/continuing/returning etc., you could just add a catch to any unknown exception and put the always code behind it. That is also when you don't need the exception to be re-thrown.

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

    So what's the problem?

    Normally finally in other programming languages usually runs no matter what(usually meaning regardless of any return, break, continue, ...) except for some sort of system exit() - which differes a lot per programming language - e.g. PHP and Java just exit in that moment, but Python executes finally anyways and then exits.

    But the code I've described above doesn't work that way
    => following code outputs ONLY something wrong!:

    #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;
    }
    
    0 讨论(0)
  • 2020-11-22 17:48

    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(); });
    
        ...
    
    0 讨论(0)
  • 2020-11-22 17:49

    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.

    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

    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.

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