Posts Tagged ‘Defer’

Sometimes we want some code to be executed after (or just before) the end of a function, also if an exeception is thrown. Suppose to deal with a C API that requires an initialization call and a termination call, something like that:


void use_C_api()

{

   Init_Lib();

   code_that_could_throw();

   Terminate_Lib();

}

The first thing a C++ programmer could think about is to encapsulate the library usage in a wrapper and use RAII (Resource Acquisition is Initialization). For example:

class LibWrapper
{
public:
 LibWrapper()
 {
    Init_Lib();
 }

 ~LibWrapper()
 {
    Terminate_Lib();
 }

 Call_code_that_could_throw()
 {
    code_that_could_throw();
 }
};

...

void use_C_api()
{
   LibWrapper wrapper; // Init here
   wrapper.Call_code_that_could_throw();
} // Terminate here (also if an exception occurs)

In C++  the only code that can be guaranteed to be executed after an exception is thrown are the destructors of objects residing on the stack, so this is our case.

Ok cool, but what if we just want to execute some code in a function? Do we have to write classes? What if we need something like Java’s finally or Go’s defer? Sometimes this kind of constructs suffice.

RAII is perfect for our purpose, but we need to create on-the-fly a sort of “finalizer”, passing it some code. Thanks to C++11 we can use lambdas to be enough generic. So, this is my idea:

template <typename F>
struct finalizer
{
template<typename T>
finalizer(T&& f) : m_f(std::forward<T>(f)) {}

~finalizer()
{
   m_f();
}
private:
   F m_f;
};

template <typename F>
finalizer<F> defer(F&& f)
{
   return finalizer<F>(std::forward<F>(f));
}

...

auto atEnd = defer( []{cout << "I'm printed out at the end of the scope!" << endl;} );

The code is trivial: the finalizer uses a template to accept any kind of “executable object” (lambdas, functions, functors, etc). I created a simple factory method to avoid expliciting the template parameter (the compiler will do the work for us).

But wait…I spot at least a possible problem in this mechanism, do you?

We’re relying on our compiler, aren’t we? What happens if our compiler doesn’t use RVO (Return Value Optimization)? We’ll be in a proper jam! We get a copy and then two destructions that lead us to a couple of calls to our at-end-code!

Is it clear? If not, try to change the factory this way and you’ll get the same effect:


template <typename F>
finalizer<F> defer_ops(F&& f)
{
   auto f1 = finalizer<F>(std::forward<F>(f));
   return f1;
}

Now our code is called twice (first when the copied object is destroyed, second at the end of the calling code’s scope). A simple way to “fix” this problem is to add a move constructor and a bool flag:

template <typename F>
struct finalizer
{
template<typename T>
finalizer(T&& f) : m_f(std::forward<T>(f)), m_isMoved(false) {}
finalizer(finalizer&& other) : m_f(std::move(other.m_f)), m_isMoved(other.m_isMoved)
{
   other.m_isMoved = true; // sort of "resource stealing" here
}
~finalizer()
{
  if (!m_isMoved)
  {
    m_f();
  }
}
private:
 F m_f;
 bool m_isMoved;
};

I don’t like this fix but it’s not possible to copy-assign a lambda-expression to another one (e.g. a no-op one – the idea would be to “reset” the moved finalizer’s m_f to an “empty” lambda). In fact the standard says “The closure type associated with a lambda-expression has a deleted copy assignment operator“.

If you’re unlucky and you compiler does not support C++11 move semantics then change your compiler! Obviously I’m joking. Let’s make another (even uglier) extra:

template <typename F>
struct finalizer
{
  // Please change it to a ctor taking a universal reference as soon as possible (when C++11 is available)
  finalizer(F f) : m_f(f), m_isMoved(false) {}

  // Please change it to a move ctor as soon as possible (when C++11 is available)
  finalizer(const finalizer& other) : m_f(other.m_f), m_isMoved(false)
  {
     other.m_isMoved = true; // sort of "resource stealing" here
  }
  ~finalizer()
  {
    if (!m_isMoved)
    {
      m_f();
    }
  }
  private:
   F m_f;
   // Please remove this mutable as soon as possible (when C++11 is available)
   mutable bool m_isMoved;
};

The key here is to replace the move constructor with the copy constructor and to add the mutable qualifier to our m_isMoved flag. Why? To be standard (the standard copy constructor receives a const&) and clear (it’s a design choice, though it is nasty).

To sum up: we started from a sort of elegant and simple code and we sank into a worse patched one. This regression may be caused by compilers that poorly support modern C++. In five minutes we wrote a simple code that worked and that solved our problem. More time was spent to fix and to meet compiler’s deficiencies.

RVO is a stupid example because I think that almost all C++ compilers support it, but sometimes you maybe need to disable it for some reason. Your code has to be prepared and must keep on working. Then be aware that design decisions are often made not only because of “core-business” but also because of your tools and facilities that are also part of your priorities just because they support your production. The more bad decisions are made because of infrastructure, the more you’re probably in trouble!

So, don’t forget to keep the barrel clean!

[Edit] Thanks to tr3w for letting me know some typos [/Edit]

Advertisements