Saturday 10 January 2015

GCC C++11 Alternative To Try/Finally

In C++11 and C++ there is no try/finally that you can use. So often you have to make classes to do cleanup for you. But with the new C++11 there are now lambdas and you can use these as bound functions that can be executed later at anytime. In the case of finally we want a lambda that does the cleanup and is executed later on at the end of the scope, just like manual cleanup of some memory or files etc...

/*
  This class defers a single void function call by wrapping it
  in a function and calling it later. Because this is C++11 you
  can pass it a lambda function.
*/
class deferred_function_call {
  std::function<void(void)> the_function;
public:
  inline deferred_function_call(const std::function<void(void)> &the_function) : the_function(the_function) { }
  inline ~deferred_function_call() { the_function(); }
};


#define _DO_TOKEN_COMBINE__(X,Y) X##Y  // helper macro
#define _TOKEN_COMBINE__(X,Y) _DO_TOKEN_COMBINE__(X,Y)

/*
  What name you use to remember something to do at the end of the scope is your choice. I like
  RememberTo
*/
#define _FINALLY(...)    deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })
#define REMEMBER_TO(...) deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })
#define remember_to(...) deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })
#define RememberTo(...) deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })
#define defer(...) deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })

#define later(...) deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ __VA_ARGS__ ; })


/*
  This macro is for calling a function pair where the second call must execute.

    For example:
      GuardedExpression(EnterCriticalSection(&MyLock),LeaveCriticalSection(&MyLock))
*/
#define GuardedExpression(expression_start,expression_finally) expression_start; deferred_function_call _TOKEN_COMBINE__(_x_temp__,__LINE__) ([&]{ expression_finally;  })

EXAMPLES


// Here is some example code that shows how to use such a macro to make your code cleanup for you
int main {

/*
  Example function calls that are made in order and must execute
*/
class TCanvas {
public:
  void MoveTo() {}
  void LineTo() {}
  void BeginDrawing() {}
  void EndDrawing() {}
  void MoveTo(int x, int y) {}
  void LineTo(int x, int y) {}
};
void BeginDrawing(TCanvas* canvas) {
  // Do stuff that happens before drawing, for example.
}
void EndDrawing(TCanvas* canvas) {
  // Do stuff that happens after drawing, for example.
}

int main()
{
  {
    FILE *file = fopen("test_Cxx11_Finally","w");
    remember_to( fclose(file); );
    printf("Do stuff with the file...\n");

    // read something
    // write something

    printf("All went well, now the file will be closed by the remember_to...\n");
  }
  {
    /* This example assumes the existence of some TCanvas object. */
    TCanvas MyCanvas;
    BeginDrawing(&MyCanvas);
    RememberTo(EndDrawing(&MyCanvas));

    printf("Drawing some stuff!\n");
    MyCanvas.MoveTo(0,0);
    MyCanvas.LineTo(100,100);
  }
  {
    /* Or you can make it call the class methods. remember_to will take any code even a block of code... */
    TCanvas MyCanvas;
    MyCanvas.BeginDrawing();
    RememberTo(MyCanvas.EndDrawing());
    printf("Drawing some stuff!\n");
    MyCanvas.MoveTo(0,0);
    MyCanvas.LineTo(100,100);
  }
  {
    /* You could guard a new/delete sequence */
    TCanvas* MyCanvas = new TCanvas;
    RememberTo( delete MyCanvas );
    printf("Executing in a guarded section!\n");
    MyCanvas->MoveTo(0,0);
    MyCanvas->LineTo(100,100);
  }
  {
    /* Or like this */
    GuardedExpression(TCanvas* MyCanvas = new TCanvas, delete MyCanvas);
    printf("Drawing inside a guarded section!\n");
    MyCanvas->MoveTo(0,0);
    MyCanvas->LineTo(100,100);
  }
  {
    /*
      This naive example shows that you can use a block of code as
      well in the remember_to clause.
    */
    int MyCounter = 0;
    RememberTo({
       // Do stuff here like a normal block, this will be executed
       // at the end of the enclosing scope.
       if (MyCounter != 10)
         printf("The code did not execute all the loop!\n");
    });
    // Here it tries to loop and the remember clause will detect if the
    // loop did every iteration it was supposed to.
    while (MyCounter < 10) {
      if (MyCounter == (rand() % 30))
        throw "Something happened!";
      MyCounter++;
    }
    printf("Everything went fine!\n");
  }

}

No comments:

Post a Comment