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 01:57

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.

查看更多
永恒的永恒
3楼-- · 2019-01-01 01:57

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;

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.

查看更多
回忆,回不去的记忆
4楼-- · 2019-01-01 01:57

I would like to provide an alternative.

If you want finally block to be called always, just put it after last catch block (which probably should be catch( ... ) to catch not known exception)

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

If you want finally block as a last thing to do when any exception is thrown you can use boolean local variable - before run you set it to false and put true assignment at the very end of try block, then after catch block check for the variable value:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}
查看更多
孤独寂梦人
5楼-- · 2019-01-01 02:00

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.

查看更多
墨雨无痕
6楼-- · 2019-01-01 02:00
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}
查看更多
君临天下
7楼-- · 2019-01-01 02:02

In C++11, if needed, RAII allows to make a 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(); }
查看更多
登录 后发表回答