As you may already know, RAII is a very powerful and popular pattern in the C++ language. With RAII, you can wrap non-stack-managed resources into a stack-managed object such that, when the stack-managed object goes out of scope, it releases the corresponding non-stack-managed object. Smart pointers are just one example of this technique, but so are IO streams too.

Before getting into the point of the article, bear with me for a second while I explain what the  stack_cleaner object of Lutok is. The "stack cleaner" takes a reference to a Lua state and records the height of the Lua stack on creation. When the object is destroyed (which happens when the declaring function exits), the stack is returned to its previous height thus ensuring it is clean. It is always a good idea for a function to prevent side-effects by leaving its outside world as it was — and, like it or not, the Lua state is part of the outside world because it is an input/output parameter to many functions.

Let's consider a piece of code without using the stack cleaner:

void
my_function(lutok::state& state, const int foo)
{
    state.push_integer(foo);
    ... do something else in the state ...
    const int bar = state.to_integer();
    if (bar != 3) {
        state.pop(1);
        throw std::runtime_error("Invalid data!");
    }
    state.pop(1);

}

Note that we have had to call state.pop(1) from "all" exit points of the function to ensure that the stack is left unmodified upon return of my_function. Also note that "all exit points" may not be accurate: in a language that supports exceptions, any statement may potentially raise an exception so to be really safe we should do:

void
my_function(lutok::state& state, const int foo)
{
    state.push_integer(foo);
    try {
        ... do something else in the state ...
        const int bar = state.to_integer();
        if (bar != 3
           throw std::runtime_error("Invalid data!");
    } catch (...) {
        state.pop(1);
        throw;
    }
    state.pop(1);
}

... which gets old very quickly. Writing this kind of code is error-prone and boring.

With an "auto-cleaner" object such as the stack_cleaner, we can simplify our code like this:

void
my_function(lutok::state& state, const int foo)
{
    lutok::stack_cleaner cleaner(state);

    state.push_integer(foo);
    ... do something else in the state ...
    const int bar = state.to_integer();
    if (bar != 3)
        throw std::runtime_error("Invalid data!");
}

And we leave the boring task of determining when to actually call state.pop(1) to the compiler and the runtime environment. In this particular case, no matter how the my_function terminates, we ensure that the Lua stack will be left as the same size as it was before.

But, as I said earlier, all this was just an introduction to the idea that made me write this post.

When you declare an auto-cleaner object of any kind, be sure to give it a name. It has happened to me a few times already that I have written the following construct:

lutok::stack_cleaner(state);

... which is syntactically correct, harmless and "looks good" if you don't look closely. The compiler will chew along just fine because, even though we are declaring an anonymous object, its constructor and destructor may be doing who-knows-what, so their code must be called and thus the "unused variable" warning cannot really be raised.

However this does not give us the desired behavior. The cleaner object will be constructed and destructed in the same statement without having a chance to wrap any of the following code, because its scope is just the statement in which it was defined. In other words, the cleaner will have absolutely no effect on the rest of the function and thus will be useless.

So, moral of the story: always give a name to your auto-cleaner objects so that their scope is correctly defined and their destructor is run when you actually expect:

lutok::stack_cleaner ANY_NAME_HERE(state);

Subscribe via RSS · Go to posts index

   Delivered by FeedBurner

Comments from the original Blogger-hosted post: