scope(exit) in C++11

I really like the scope(exit) statements in D. They allow you to keep the cleanup code next to the initialization code making it much easier to maintain and understand. I wish we had them in C++.

Turns out this can be implemented easily using some of the new C++ features. I'm not a big fan of C++11; for the most part I think it adds even more complexity to an already complex language. However, I have to admit that some of the features can be useful, when used with care and moderation.

I think this is one of such cases. If you can get past the weird syntax, lambdas can be very powerful. What I find most useful is to be able to define local functions (closures) that can access the local variables of the enclosing scope.

In our case, what we want to do is to define some code that is executed at the end of the scope to cleanup objects allocated in the current scope. This is easily achieved allocating an object on the stack that invokes a lambda function that wraps our custom cleanup code:

#include <functional>

struct ScopeExit {
    ScopeExit(std::function f) : f(f) {}
    ~ScopeExit() { f(); }
    std::function f;
};

#define STRING_JOIN2(arg1, arg2) DO_STRING_JOIN2(arg1, arg2)
#define DO_STRING_JOIN2(arg1, arg2) arg1 ## arg2
#define SCOPE_EXIT(code) \
    ScopeExit STRING_JOIN2(scope_exit_, __LINE__)([=](){code;})

Then you can write code like:

int * ip = new int[16];
SCOPE_EXIT(delete [] ip);

FILE * fp = fopen("test.out", "wb");
SCOPE_EXIT(fclose(fp));

I know you can go to greater lengths to mimic the D syntax, and that Boost also provides similar functionality, but this does all we need in less than 10 lines of code.

Sadly we cannot really use this on The Witness, because I'm not sure we are yet ready to drop support for platforms and compilers that do not implement the required C++11 functionality.

Update:

As mentioned by Daniel in the comments, you don't really want to use std::function in this case. A quick look at the assembly output confirms that the generated code is horrible. It makes a lot more sense to wrap the closure explicitly and rely on type inference to allocate the wrapper object on the stack:

template <typename F>
struct ScopeExit {
    ScopeExit(F f) : f(f) {}
    ~ScopeExit() { f(); }
    F f;
};

template <typename F>
ScopeExit<F> MakeScopeExit(F f) {
    return ScopeExit<F>(f);
};

#define SCOPE_EXIT(code) \
    auto STRING_JOIN2(scope_exit_, __LINE__) = MakeScopeExit([=](){code;})

61 Comments:

  1. I’m sure you know this, but for the record : while C++11 Lambdas make this a lot easier & neater, it certainly can be done in older C++ , you just have to put the arguments into a template functor object.

    (of course this is just what Lambdas are doing for you)

    For example in cblib/Callback.h you can use

    ON_DESTRUCT1(fclose,fp);

    to get the same thing. (Boost has the same kind of thing)

    It requires a mess of template wiring, because old C++ is missing the very basic feature of being able to store a function + all its arguments in a struct for later use.

    I do think it’s worth investing in that wiring though, because it lets you do things like run functions at later times, or on other threads, etc.

  2. Hi Ignacio,

    Core language support on gcc, msvc and clang seem to be getting there, although their STL implementations are lagging. I’m stuck with gcc 4.5 in production, but the features I do have access to I’d find it hard to live without now. In particular, r-value references and auto have had a huge impact on my coding style.

    I’m curious – are there any C++11 features that you have found to be well enough supported for use in your production code? Are there any new language features that you’d be especially keen to use if platform support was not an issue?

    – Mark

  3. You can do this using a function inner class. I could have sworn I wrote a blog entry about this a while ago but I’m not finding it now.

    Boost has some wrappers to make it easier.

  4. Charles: I hadn’t thought about doing it that way. Relying on the virtual destructor for the cleanup is an interesting idea. I had been trying to allocate the Callback object on the stack directly, which requires some form of type inference and complicates things too much.

    Mark: I’ve just started playing with C++11. Native type inference is long overdue (decltype). A few years ago I would have been very excited about it. Today I use a very small subset of C++ and don’t have that many uses for it anymore. Personally, I’d take support for modules and faster compile times over any of the new C++ features.

  5. You really don’t want to use

    std::function

    for anything like this. It will almost certainly incur a heap allocation, since capturing all the locals will exceed any small buffer size that your implementation might use internally. In C++03 you had to resort to using the fact that a const reference to a temporary will keep it alive for the duration of the scope, but in C++11 you can just use

    auto

    instead:


    template
    class ScopeExit {
    public:
    ScopeExit (F f) : m_active(true), m_f(f) {}
    ScopeExit(ScopeExit const& other)
    : m_active(true), m_f(other.m_f) {
    other.m_active = false;
    }

    ~ScopeExit () {
    if (m_active)
    m_f();
    }

    private:
    mutable bool m_active;
    F m_f;
    };

    template
    ScopeExit MakeScopeExit(F f) {
    return ScopeExit(f);
    }

    #define SCOPE_EXIT(code) \
    auto STRING_JOIN2(scope_exit_, __LINE__) = MakeScopeExit([&](){code;})

    Note the use of

    [&]

    for the lamda capture. You don’t want to capture by value (

    [=]

    ) here since copying locals can be very expensive (and often incorrect).

    • No, you really want to capture by value, otherwise imagine what would happen if:

      ip = new int;
      SCOPE_EXIT(delete ip);

      ip = new int;
      SCOPE_EXIT(delete ip);

      You would delete the same pointer twice.


      • ip = new int;
        SCOPE_EXIT(delete ip);

        ip = new int;
        SCOPE_EXIT(delete ip);

        This code is conceptually invalid, since the first “SCOPE_EXIT” takes ownership of “ip”, which leads to a race-condition with the second assignment “ip = new int;”. So you generally want to use a reference capture for the scope exit (and scope guard) idioms.

      • By value doesn’t work in some cases either. For example

        ifstream stdin(“/dev/stdin”);
        SCOPE_EXIT(stdin.close());

        This fails because you are not allowed to copy ifstream objects. Maybe you need two forms of the macro, one that uses [‘&’] and another that uses ‘[=]’. In the specific case where you are using pointers which you must delete, then it makes sense to take the pointer by value. But the reference form is very useful too.

    • BTW, I just took a look at the generated assembly and it’s horrific. For some reason I thought std::function would do some type inference magic internally, but that’s clearly not the case. Better to do it explicitly. Thanks for pointing it out!

  6. Cool – that’s a really nice syntax – keep deallocation / cleanup together with the declaration. Wonder if there’s a way to set that with C# (their using{} statement is a similar idea but less elegant, requiring an indented block). Something to try whenever I get motivated for game development again… :)

  7. You can also use std::unique_ptr and specify a custom deleter to deal with resources that are not dynamic memory (see this post ). So your examples could be like:

    unique_ptr ip = new int[16]; // no need to specify the "delete[]" at the end

    and

    class FileDeleter {
    void operator()(FILE* fp) { fclose(fp); }
    };
    unique_ptr fp = fopen("blah");

    or even

    unique_ptr fp( fopen("blah"), fclose)

  8. Sorry for the broken HTML in my previous reply, reposting.

    You can also use std::unique_ptr and specify a custom deleter to deal with resources that are not dynamic memory (see this post ). So your examples could be like:

    unique_ptr<int[]> ip = new int[16]; // no need to specify the "delete[]" at the end

    and

    class FileDeleter {
    void operator()(FILE* fp) { fclose(fp); }
    };
    unique_ptr<FILE, FileDeleter> fp = fopen("blah");

    or even

    unique_ptr<FILE, int(*)(FILE*)> fp( fopen("blah"), fclose)

  9. std::unique_ptr will take care of this for you. You’ll need to define a one line structure to handle custom deleters like fclose, but it’s really pretty simple. (I’m sure they had a good reason to go that route instead of bringing over boost::scoped_ptr, but I really liked scoped_ptr.)

  10. Hi ignacio,

    we have been using this for the last 3 year in “old” c++ code. It can be definitely implemented:

    http://www.ddj.com/cpp/184403758
    http://www.zete.org/people/jlehrer/scopeguard.html

  11. Hi, I have a question about that syntax. Why are you using 2 #defines instead of something like that #define STRING_JOIN2(arg1, arg2) arg1 ## arg2 ?

  12. Go has defer which is handy.

  13. Maybe for an arbitrary chunk of logic, but in the case of the examples given std::vector and pick your favourite RAII file handle wrapper.

    For my taste the solution given uses far too much (read: any at all) pre-processor machinery.

  14. I’ve usually found it better to just use shared_ptr for this sort of thing:

    typedef std::shared_ptr FilePtr;
    FilePtr f(fopen(“test.out”, “wb”), fclose);

    When it goes out of scope, it will call fclose instead of delete, but if you did decide to pass that file pointer around or store it somewhere, the custom deallocator goes with it as a hidden implementation detail.

  15. **That should be:
    typedef std::shared_ptr FilePtr;

  16. I have to say, this is the worst C++ code I’ve seen in a long time. C++ does not need this language feature, RAII does this for us. (HAIL RAII!) I cannot come around the fact that writing a simple wrapper seems so much more elegant than a bunch of magic macros, which I have to think about every time, and which aren’t even necessarily exception safe, as someone could easily write
    int* a1 = new int[100];
    int* a2 = new int[100];
    SCOPE_EXIT(delete [] a1);
    SCOPE_EXIT(delete [] a2);
    which is obviously not exception safe.

    • Well, for one, I personally do not like exceptions and do not ever use them, because I think they lead to bad code. So if you disagree on that, we are just coming from different worldviews.

      All I can say is, hey, with The Witness we are successfully writing a pretty complicated game and engine, with a small team, that most people (including most language-opinion-pundits) would find difficult to get working at all. So we can’t be doing things *too* wrong. Empirical results are the ultimate judge.

      If I want to be offensive, my standard reply is to ask how much real code you have written, what is the largest/most complex program, etc. In my experience these “your code sucks!!” comments come from people who are relatively new to programming (I used to make those kinds of comments, back when I was recently out of college, etc).

      People who have dealt with big and complex programs tend to be more chill about these kinds of issues because they know what real technical problems look like, and they know that while language and feature choices can create or remove friction, the biggest problem is not the O(n) friction, but the super-linear growth in program complexity that quickly dwarfs friction.

      • If you’d follow the simple rules that everything you want to do any time you leave the scope should stand in a destructor, and every destructor should only be responsible for one manual delete, your code would be safe and easy without any macros whatsoever.
        The program complexity should actually grow much slower this way, as all the classes are reusable, safe, and one doesn’t have to write the same cleanup code over and over again. If you work with many different handles and don’t want to repeat the move/copy boilerplate code, I suggest using std::unique_ptr and maybe a small self written unique_handle class. (I assume you’re familiar with the arguably best C++11 feature there is: move semantics.) Wrappers like that provide a safe and convenient way to deal with almost every resource there is. (From file handles over window handles over device contexts to OpenGL handles.)

        • Here is what I am trying to say:

          Suppose the function for overall complexity of your code is a * (N ** b), where N is the number of functional components (measured in some very vague way), and 1 < b < 2. You are talking about ways of reducing a. I am saying, sure, it is fine to reduce a, but it is way more important to reduce N or b, and often, proposed methods of reducing a actually increase N at the same time (or even increase b), leaving you in a worse situation for large programs. It is a delicate situation.

          • How could you even possibly come to the conclusion that my method reduces a? In any scenario, the creation of reusable classes reduces the exponent, since one may has to write more code up front, but relatively less the more the class gets used. But honestly, since there is not really an popular “code-complexity-model” whatsoever, any discussion on this level seems totally useless. I could now just say the complexity gets modeled by a^x+bx^2+cx+d or whatever, and my method reduces x and yours d. No facts involved, just bullshitting. So please lets keep it straight near the facts, that are:
            With RAII:
            – the cleanup code only has to be written once.
            – the cleanup is exception safe.
            – one cannot forget to write the cleanup. (Or the mistake will be caught very early since nothing gets freed, and it only has to be corrected once.)
            – The ownership relationships are already modeled through the class, so the resource protection works over simple scope boundaries.

            After all, RAII with a few simple rules (already named those in my first post) makes it almost impossible, if not completely impossible, to get resource leaks or anything like that; at the expense of a few lines of code for the class boilerplate. Your method offers none of these benefits.

          • I am going to give up on explaining this. The model I mentioned has very specific motivation. Of course it is not some kind of precise model, but if you are a good programmer you will be able to understand the motivation by looking at it. It’s not a random polynomial. Why, for example, do you think that 1 < b < 2? Think about it. If you understand what that means, really, then we can have a productive discussion.

    • Did you read the article? The macro is precisely creating an object that takes ownership of the pointer and executes the cleanup code in the destructor at the end of the scope. You can of course do the same explicitly, but my point is that using closures with the proposed macro makes this a lot easier, more succinct, and much less error prone.

      • Using an fstream is even less error-prone because you don’t have to remember any cleanup code.

      • No, it is much _more_ error prone, because one has to think about writing the cleanup stuff every time a resource is used! And it’s even easy to make mistakes if you _don’t_ forget to write the cleanup code, because the code doesn’t enforce coupling between reservation and release of a resource, so the cleanup code may not even be triggered with exceptions. (As I’ve shown before. And yeah, you don’t like exceptions, lol.) It makes absolutely no sense. C++ already has a very good, if not even the best currently known way of handling resources. This macro gets totally useless as soon as you have resources inside a container or any ownership transfer whatsoever by the way; while RAII just works fine – always.
        By the way: You do realize that new can throw? Do you guys always use the nothrow version in your project? Did you turn off exception handling in the compiler, so you at least get the small speed benefit from not having exceptions?

  17. I think it would make more sense to use something like the solution Jorge pointed out (unique_ptr with custom deleter). It’s basically accomplishing the same thing, but lets you define a few different classes for common resource types (HANDLE, etc…).

    This way you no longer need to write the explicit custom clean up code for each resource you’re allocating. That’s one less thing to get wrong.

  18. I agree – it’s like he’s realised the need for RAII, but not understood how C++ already provides more elegant solutions for it.

    So instead of using C++11 as intended (or even C++98, though it’s with C++11 that this really becomes easy) – all resources encapsulated in classes which free them in their destructors, and making use of unique_ptr/shared_ptr for simple cases) – he’s continuing a C-like approach, but making use of advanced C++11 features to make it not quite so dangerous, but still requiring a more error-prone+verbose multi-line pattern to be followed everywhere than would be required with RAII (plus preprocessor tricky which unlike you I believe there is a case for sometimes, but only when needed, which given the better solutions it isn’t here)

  19. Some problems I see with this approach :
    – it separates the moment of binding between the resource and its destructor. The code is very fragile under maintenance, since it’s very easy for someone to do something like this :

    // before
    FILE* fp = fopen(...);
    SCOPE_EXIT(fclose(fp));
    //
    // Later, a time pressed programmer, needs to make sure the file meets certain requirements before // being used
    FILE* fp = fopen(...);
    if (!file_requirements_are_met(fp))
    return false; // oops, fp leaks now
    SCOPE_EXIT(fclose(fp));

    This problem is eliminated with unique_ptr.
    – ugly macros, meaning harder to debug
    – cannot be used in pre C++11 code, unlike unique_ptr
    – probably some loss of speed, since the lambda must ref capture all locals (maybe the compiler can optimize this, I wasn’t curious enough to look at generated assembly code)

    Applying resource management idioms from other languages to C++ is really the wrong way. C++ has always had great resource management capabilities. It only took people about 10-15 years to discover that :)

  20. IKnowImOffensive, are you trying to be helpful here or just trolling, I really can’t tell.

    Sometimes you sound like you’re really trying to help out, and other times it sounds like you’re just trying to save face.

    • I can’t really do something with this comment.. please refer to something I wrote if you think it’s wrong or worth discussing.

      • I hear you, but I have a few problems with what you’ve said. I don’t know that anything you’ve said is actually wrong, and certainly don’t know if finding out whether you’re genuinely trying to help is worth discussing. I did consider it to be worth asking you directly, but you don’t seem to want to answer it.

        The reason I’m confused is that you entered the conversation about this post with “I have to say, this is the worst C++ code I’ve seen in a long time.” Maybe it’s just me but this doesn’t seem constructive at all. I could certainly write worse C++ code any time.

        It seems its only purpose is to imply that the Witness team need lecturing from you so they can be set right. Even that on its own wouldn’t bother me so much if that team weren’t so clearly a group of friendly, fantastic people. I don’t know Ingacio at all but he seems like an extremely gracious dude, and beginning your lecture like that doesn’t leave much room to take you constructively.

        On top of that, programming is only one part of what they’re trying to accomplish here. It’s possibly analogous to arguing with a film crew about their equipment. It’s important that it’s good, but it’s not important that it’s flawless (Witness crew: feel free to disagree with me on that!)

        There are also matters of story, artwork and gameplay they have to build. You seem positively outraged by their code, but I can almost guarentee that when we’re all playing the Witness, not a single one of us will be able to tell what the programmers finally did in the end.

        That’s not to say that this issue Ignacio is writing about is not important, absolutely it is, but to call his code the worst you’ve seen truly does seem to offer little to anybody except possibly your own ego.

        I could certainly be wrong about that, and you could be offering your help here contructively. I didn’t want to just accuse you of doing so, which is why I asked the question in the first place.

        I hope that helps clarify my first comment.

        • sebastian, the thing is, this kind of response is common among young programmers. You can’t take it too seriously. I used to do the same thing. In this lecture there’s an anecdote, from back when I was in college or just out, where I tell one of the Id Software guys that they are lazy programmers and their code is bad, because they weren’t doing things the way I had been taught was The Right Way…

          http://mort8088.com/2011/08/10/jonathan-blow-talks-to-csua/

          … while, of course, they were programming stuff that was way cooler than I was able to program.

          So, I have learned to expect this kind of thing. It’s the circle of life.

          But, now that we have the Internet, I would hope that people have enough self-awareness to see this happening over and over, notice it in themselves, and chill out a bit. Maybe that is too optimistic to expect, I don’t know!

          • (and this talk may also explain why my idea of ‘bad code’ doesn’t necessarily coincide with conventional, popular ideas).

        • Also, if this is the worst code he has seen, he really hasn’t seen much code.

          I mean, seriously.

          • I just listened to that talk, and I have to say it must be surreal for you to now be Id, fielding complaints from young Jonathan Blow’s!

            But yes, it’s uncanny how much that talk describes this conversation so precisely.

        • I never made a comment about the game whatsoever. I also don’t think that things like that will necessarily make the game much worse or even bother anyone if the game is released. To be completely honest, when I wrote my first comment, I didn’t even realize that this blog is about a game. I just got redirected from isocpp.org, and I certainly do _not_ think that this is good C++ and I don’t think it’s a good thing to teach this as good C++ to beginners. That’s why my first comment was so offensive. I don’t like it when people write about things which are obviously out of their expertise. I don’t have a problem if a games programmer writes something about graphics, spatial partitioning, physics in games, etc., and the code in the article isn’t exactly neat. But if you’re going to write about how to write good code, you should know how to write good code. _And_, maybe even more important, you should really care about what you write, because otherwise it doesn’t really make much sense discussing it. This whole “yeah I don’t really care about the theory and I just want to get things done” argument is fine, as long as the whole purpose of your article isn’t about how to write good, idiomatic code.

  21. Careful! That last snippet is allowed to invoke the cleanup function more than once, even though I doubt it will happen in any mainstream implementation. It appears to be working right now because of copy elision. If copy elision were not to happen, there would be two copies of the ScopeExit object around, and then the cleanup function would be called once when the function return and again at the expected time. To fix this you should add a move constructor and a way to track if the object has been moved.

    • This is a very good point. Yes, I’m relying on copy elision, which in theory the compiler is not guaranteed to perform. However, it’s unclear to me how would you correctly implement the move constructor of ScopeExit. Lambdas have implicitly deleted default constructors so there’s no obvious way of reseting the lambda of the rvalue after it’s been copied. Can you shed some light on this? It’s kind of strange, because it seems that without copy elision optimization the code would fail to compile. Since code elision is permitted by the standard the code is actually valid, but only on compilers that support it. This is the kind of thing why I say C++ is too complex and C++0x doesn’t do much to alleviate that.

      • Can it be done by writing an alternative class which doesn’t have a c’tor or would that simply push the problem along? Here’s how it might work:
        1) create a copy of ScopeExit (e.g. class ScopeExitTransfer) which doesn’t call f in its d’tor;
        2) return a ScopeExitTransfer – instead of ScopeExit – from MakeScopeExit;
        3) add a c’tor to ScopeExit which receives a reference to ScopeExitTransfer and copies f over.

  22. I think any decent compiler can do RVO here.
    A great use for this idiom (among others) would be to implement some form of design by contract :

    int func(int *arg1, int *arg2, const char** arg3) {
    ON_SCOPE_ENTER(
    assert(arg1),
    assert(*arg1 == -1),
    assert(arg2),
    assert(*arg2 == -1),
    assert(arg3),
    assert(*arg3 == nullptr)
    );

    ON_SCOPE_EXIT(
    assert(*arg1 > 0),
    assert(*arg2 < 0),
    assert(*arg3 != nullptr)
    );

    *arg1 = +24;
    *arg2 = -10;
    *arg3 = "whatever";

    return 0;
    }

  23. Well, there’s two types of code :
    – theoretically corect code
    – practically correct code.
    This piece of code is practically correct, on every decent compiler out there. Theoretically it may not work as expected. Alas, in the real life, practicality wins over theory, otherwise it would be very difficult to get things done. There’s no black and white stuff, only shades of grey.

  24. This is like a small itch I just can’t resist scratching. If you truly are a troll, you’re a very good one. I want very much to just ignore your comments and apparent ignorance, but the promise of how good it will feel to scratch the itch is just too great.

    I read over the original blog post three times since reading your response, just to be absolutely sure I was seeing what I was seeing. And, sure enough, right above the beginning of the post, in quite large typeface, are the words “The Witness: An exploration puzzle game…” and I have to say I’m just completely baffled why you would find this post and be compelled to insult the author with no knowledge of what he was trying to accomplish with his code.

    But, even if you truly did just not see the clear header indicating this is a game, I can’t find a single part of the original blog post where Ignacio claims to be teaching anything. It’s little more than writing about his discoveries and frustrations and being open about it. To attack somebody for that just seems so petty. But, that aside, Ignacio is making an ACTUAL videogame. One which I– and I would imagine every single other person reading this blog– am going to happily give money for when they’re done.

    Incidentally, the entire conversation that took place in the comments (besides your own input) has clearly been extremely helpful for the team, AND for anybody who happens to want to learn how to code. I mean, my god, the pettiness you’re displaying.

    Man it feels nice to scratch.

    • oh no… sorry thefatredguy, I think I just replied to you, instead of IKnowImOffensive.

    • I still don’t get your point, the code is bad, I’m right, and I even was so kind to show how to do it better. You don’t seem to realize I really don’t care about the game, it may be good, but that will probably have nothing to do with how they handle resource cleanup in their code. PS: I never read blog titles, I’m just interested in the article itself.

      • I regret becoming involved with this conversation. My response yesterday was silly and I see now that I should have just listened to Jonathan and not taken it all seriously.

        I’m bewildered by your ability to not get anything but your own messages, but I foresee some serious diminishing returns if I continue to engage, and those returns were quite fleeting to begin with.

  25. My personal feelings about new features in programming languages, after 15 years of experience in C++, C#, and 2 years of java, is that they can be useful but often it’s difficult to know that they exists, you need to think about the best use for them (because usually real examples are few) and many times the algorithms are difficult to port to other languages or other enviroments if they use uncommon practises. Another aspect is sharing the code with other people: if you use more and more (new or less popular) features, will people understand why there’s that snippet of code there? Certainly you can document/comment everything and people need to update their knowledge but in the real world, in my experience, things are less ideal.

    A similar situation to the various new C++ features has happened when Microsoft has released C# 3.0. There are so many new features like anonymous classes that can speed up coding but are they really necessary? Usually a better analysis of the problem is better than using something that feels, to me, hacks. This is confirmed when you need to improve the software with new features and those lines of code need to be (totally) refactored.

    For reducing errors in initialization and cleanup of objects i try to encapsulate the most critical operations in common classes, and separate the real computation in another class.
    For example we need to read/write a file: i usually create a class that implements opening/reading/writing/closing/IO error handing. The class receives the specific parsing/writing class instance i want to execute. The first class will open the stream, than it will use the second class to execute calculations. The inner class can access to the IO functions of the outer class. After than it will cleanup instances.
    If something fails at IO level the outer class will manage the IO objects cleanup, the inner class the calculation cleanup. The cleanup is used also when all is successfull, always separated. So things to manage are better focused to the scope of the class itself.
    Another example is wanting to create a thread that calculates something and that shows a progress bar. The outer class will manage thread creation/cleanup, interfacing with the UI, the inner class just perform the calculation, signalling the outer class for progress, error, end of the processing.

    I’d love to see some kind of compatibility profile in C++. At default you can use any classes and features from C to newest C++ but an option can limit the compiler to accept only the most updated/ANSI features (someway similar to OpenGL profiles). So it’d force people to program with better standards and compilers would optimize those features instead of having to mix both old and new. Now you can have only deprecation warnings for some old unsafe functions.

  26. From the perspective of a young programmer I must say it’s easy to influence my opinion. In my mind nothing is set in stone and following advice of other experienced programmers is hmm… desirable. It’s always harder to enter a world full of features than enter a very simple world and grow your experience alongside feature expansion.

    I have to admit, that the idea of simplifying coding rules is close to my heart. Reading code full of ornaments and obscurity is quite hard. That’s why I find beauty in code like the bitsquid foundation library, that was recently released and in coding styles proposed by Noel Llopis. However there is some boundary I wouldn’t like to cross. One of such things are global variables. Why am I hesitant to use such things for development? Again because reading & reasoning about code, that uses it, is really hard. Also removing functions because raw code is more readable is a dangerous topic to me. I find small functions really readable, I like when a function has no side-effects and it does exactly the job it’s name describes. So if a higher level function is suppose to just invoke two other functions then perhaps there is no reason for this function to exist, but just maybe it should be there because all these functions are semantically from different systems? I am wandering off here, away from Jonathan’s presentation & back to my text.

    In this post Ignacio suggests D-style closures to solve the problem of scope cleanup. My observation is, that c++ is not D, and maybe, just maybe a scope exit statement in C++ is present but in a different form?

    I don’t know the first thing about D but I find it very hard to see the proposed solution to be any superior to smart pointers (not referring to boost smart_ptr).

    I mean I know, that you guys play by the KISS(Keep it simple, stupid) methodology. But isn’t the proposed solution of C++ MACROS, lambdas, hidden RAII classes doing just the opposite thing? Is it actually, not against your ideology?

    Why not choose the simplest mechanism c++ has to offer? Either built-in std::unique_ptr or witness::scoped_ptr ? Why abstract the difference between C++ & D, shouldn’t we embrace it? Every abstraction has it’s strings attached, and I see several strings attached to the method proposed. It doesn’t transplant to c++ without sacrifices and if there are methods in c++ without sacrifices, why not use them?

    This is my first impression, but bear in mind I am in no way as experienced as You guys are and have never coded anything as large, complicated or cool as You have.

  27. I would use a wrench… OR maybe a fork. But, i trust in you guys!

  28. While I like the article I kinda agree with IKnowImOffensive, not in that the code is some how bad (it works, although it’s not the most straight forward), but I do think it’s much cleaner and safer to use RAII from a maintainability perspective that what’s described here.

    (On reflection however, IKnowImOffensive’s exception-safety argument may not hold much water however since, if the PS4 compiler is anything like the PS3 one, exceptions wouldn’t really be supported very well (& typically haven’t been in console game dev for a very long time), if at all, and thus are likely not something game developers can hope to rely on in production code. So considerations towards this requirement are kinda moot.)

    You could still pass your custom d’tor code (if required) as a lambda into the RAII object constructor, effectively again leveraging the benefits of C++11 but also make your code far simpler (you only write your single line c’tor statement and any scope-based cleanup just magically works without you having to write a single line of extra code).

    It’s an interesting idea though however not quite one I’d go for.

  29. I prefer my solution involving std::unique_ptr (and I hope your HTML parser in the comments form doesn’t destroy the template syntax):

        /* Open file */
        struct fcloser { void operator() (std::FILE* fp) { if(fp) std::fclose(fp); } };
        /* The std::unique_ptr ensures that the file is automatically closed, */
        /* no matter which path this function is exited.                      */
        std::unique_ptr fp { std::fopen(fn, "rb") };
    

    Or if you don’t care about the null-pointer check, just call fclose directly:

        std::unique_ptr fp( std::fopen(fn, "rb"), std::fclose );

    No #defines needed.

  30. Well, of course your blog’s comment section’s HTML parser destroyed the templates in my previous post. Let’s try again more inconveniently. This time you have to read and understand that the [ and ] are actually angled brackets. The post content is otherwise the same.
    —-
    I prefer my solution involving std::unique_ptr (and I hope your HTML parser in the comments form doesn’t destroy the template syntax):

        /* Open file */
        struct fcloser { void operator() (std::FILE* fp)
            { if(fp) std::fclose(fp); } };
        /* The std::unique_ptr ensures that the file is automatically closed, */
        /* no matter which path this function is exited.                      */
        std::unique_ptr[std::FILE, fcloser] fp { std::fopen(fn, "rb") };
    

    Or if you don’t care about the null-pointer check, just call fclose directly:

        std::unique_ptr[std::FILE, int(*)(FILE*)] fp
            ( std::fopen(fn, "rb"), std::fclose );

    No #defines needed.

  31. Been reading lots about Blow’s aversion of RAII, and people not getting it.
    I think it’s a failure of context-filling on Blow’s part, and I think I get it:
    – RAII is really good for scenarios of many short-lived ‘resources’ that are sprinkled all over a large code-base, with many possible exit-points for the scopes in which they are allocated. That is the general use-case of most resources in a code-base that is NON-performant-critical.
    – In performance-critical code, you tend to have a very different and disciplined resource-allocation story, in which you try to do the greatest-majority of resource-allocation that you possibly can “up front”, in very long-lived objects, that have very limited/constrained/well-defined allocation-point and release points, that are meticulously/strategically placed/planned.

    These are 2 very different memory-allocation patterns, which means they have very different solutions that cater to them best.

    Blow is talking about how RAII entails a complication that is unjustified in his domain, since RAII doesn’t benefit that much for his domain, yet demands a lot from the developer. For the use-cases that RAII is helping, it more than compensated for the complexity-cost, by paying-off dividends in reduced overall-complexity of having to manage resource-releases manually “repeatedly” in many places. In Blow’s use-case, it can actually interfere, since that use-case actually does NOT need the resources to be released on every block-exit, since re-allocation for that same resources a while later (say, in the next frame), would be too expensive (especially when multiplied times the number of resources that need to be allocated). Game-developers are fighting over nanoseconds to be able to finish rendering a complex frame in <16ms. These reallocations just pile-up very quickly, so they should be avoided – and RAII just pulls in the complete opposite direction, su it is a bad fit for the use-case – you want to be able to reuse as much resources as you can, so you don't want them to even be 'accidentally' released by RAII rules, when some developer is not paying attention…

    Anyways, I'm not a game developer, and do not consider myself a C++ expert, but that is how I understand it.

    • Not really.

      The issue isn’t that “my domain” is so much different from most other domains (though it is a bit different). The real issue is what standards people have over how good their work has to be.

      A lot of code that ships today is soft-real-time code. For example, every app that runs on a phone. But then, you go and try to use Android and it is bloated and horrible, yet the hardware it’s running on is a very very fast computer compared to what I had to work with in the year 2000 early in my game-making career. What is wrong here? Pretty clearly something is horribly wrong.

      I don’t see that you can seriously claim that people are successfully building software using RAII. Phones are horrible and slow. The Web is horrible and slow and buggy and barely works. etc, etc. You can only make these claims if you have very low standards for working software.

      In reality, RAII (especially when combined with some other things like exceptions) bloats your code and makes it much harder to understand, which exacerbates all of the in-real-life performance problems listed above.

      I think that by the time we solve these performance problems, the solution we end up with does not look much like RAII.

      • If I can be even more clear:

        This is not a case of me saying “People using RAII are doing a totally great job in their domain, but that is different from my domain”.

        I think people using RAII are doing a bad job in most domains. They are not being good programmers. They have mistaken ideas about what successful programming is and what good code is like. Yes, it will make a lot of programmers mad to hear I think this. That is fine.

        There are probably a few domains where I’d consider RAII a reasonable idea: for example, maybe you are building medical equipment or the Space Shuttle, and you are using a language like Rust, and you are optimizing your code so seriously that there is no question about performance being an issue (note that for these really serious applications, like medical equipment or the space shuttle, good performance is absolutely critical). In a language like Rust, that is trying very very hard to ensure safety all the time, RAII makes more sense. In a language like C++, RAII is just kind of a joke and you can’t use it the way the language intends without writing kind-of-terrible code. In my opinion.

  32. Hey guys, seems this article is still getting the odd view despite its age, so thought I’d chime in. Some interesting discussion in the comments here (ignoring the trolling). If you are now at the point where you can use C++11 in your code, then the comments from danielw and the ScopeGuard link from Ent are pointing you in the right direction. The ScopeGuard stuff in particular deserves some closer scrutiny, especially the revised version of it which uses C++11.

    I recently wrote a detailed pair of blog posts about this whole area, starting from a similar place as you with std::function and working through the steps to arrive at why the ScopeGuard-like approach is generally superior in all respects. The end result of those articles has the following features:

    – It is efficient, avoiding any dynamic memory allocation and minimises memory footprint.
    – It is robust, using an RAII-based technique to ensure cleanup code is executed
    – It results in concise code with a high degree of readability. No macros are required.

    You can find the two articles here:

    Let your compiler do your housekeeping

    OnLeavingScope: The sequel

    The latter of these two includes links to the original work by Andrei Alexandrescu and an active proposal to the C++ standards committee for scope_exit which is essentially the same as Andrei’s latest ScopeGuard implementation. I hope you and others find these two articles useful.

Leave a Reply

Your email address will not be published.