1. 49
  1.  

  2. 6

    Yeah I know I knooow… theoretically RAII is about general resource management, not just about memory. But at least in my experience, it’s always about memory management.

    I see RAII used a lot when you need to track resource clean up like files or sockets. You can also use it to bound objects to a specific scope, such as scope mutexes so you don’t forget an unlock. It has the advantage over some GC systems in that you know when destructors will be called for resource release (except for the order of destruction of static variables) (I’m saying “some GC” because I’ve heard of some identifying variables they can put on the stack).

    RAII is that it is mostly useful in the same situations where garbage collection is useful: automatic memory management - more specifically keeping track of myriads of tiny memory allocations and deciding when it’s safe to free them.

    Garbage collection or RAII are certainly great features to have - assuming having tons of small memory allocations to keep track of is nothing to worry about.

    If you don’t have such small (and often hidden) memory allocations happening decentralized all over the place in the first place, both GC and RAII lose most of their appeal.

    The smart-ass advice is of course “don’t do many small memory allocations”.

    If you don’t have such small (and often hidden) memory allocations happening decentralized all over the place in the first place, both GC and RAII lose most of their appeal.

    Smart pointers don’t have to allocate all over the place. You can have an object return a handle which is given memory out of a predefined pool and gives it back to the pool when it’s destroyed (pool allocator). Since you ask the OS for the memory in one big chunk when the pool is created, this can be quite fast.

    RAII is nice to have. It’s not perfect, but there is a lot more benefit to it than that article suggests.

    I use C++ a lot, but I also use C, because nearly every language can talk to C.

    1. 3

      Smart pointers don’t have to allocate all over the place. You can have an object return a handle which is given memory out of a predefined pool and gives it back to the pool when it’s destroyed (pool allocator). Since you ask the OS for the memory in one big chunk when the pool is created, this can be quite fast.

      That’s still allocation – and generally, this is roughly how a modern malloc with segregated freelists works. One place where you can win over modern mallocs consistently and generically is if you have a region allocator, where you do bump allocations to allocate, but don’t free individual objects – instead, you only free the whole pool.

    2. 6

      Modern C basically doesn’t exist for anyone but users of GCC and Clang. I tend to use C when I need code to be portable, and Modern C (e.g “C99”) conflicts with that goal. Even MSVC doesn’t support C99. Yes, GCC and Clang are pervasive but C89 is another order of magnitude more pervasive so I stick to C89 if I need to write C.

      1. 5

        I think I could benefit from essentially the inverse premise

        1. 2

          You’re in luck because someone did exactly that:

          https://ds9a.nl/articles/posts/c++-1/

          It’s a multi-part tutorial but it’s pretty good. Keep in mind that C++ is a huge language and this only touches up on a small subset of C++.

          1. 1

            I went through that about a year ago (I had experience with C89 and pre-standard C++ but have been all Java since the late 90’s). My biggest difficulty was all the different standards that are available. Stroustrop 4th edition for C++11 (C++0x) along with a C++ Primer 5th edition got me started on the right path, and now CUDA is forcing me to learn C99.

          2. 3

            I’d say memset every struct you allocate, and always initialise to 0 all stack primitive variables. So many times I’ve seen failure due to refactor that change the control flow where the refactor is slightly broken because a variable is uninitialised in one of the flow path (even with warnings).

            On the subject, I like the pattern of having:

            • void my_type_init(my_type*, args...) and void my_type_deinit(my_type*) for initializing/deinitializing internal state but makes no assumption if the pointer is on the stack/heap.
            • my_type* my_type_create(args...) and my_type_destroy(my_type*) does the allocation on the heap and internally calls init/deinit.
            1. 4

              From here:

              The worst part of all this is that required initializers prevent compilers and static-analysis tools from finding real uninitalized-variable errors for you. As far as they’re concerned it was initialized; they don’t know that the initial value, if left alone, will cause other parts of your program to blow up. If you need a real value, what you really want to do is leave the variable uninitialized at declaration time, and let compilers etc. do what they’re good at to find any cases where it’s used without being set to a real value first. If your coding standard precludes this, your coding standard is hurting code quality.

              1. 3

                Another think to keep in mind is that C99 allows one to declare variables anywhere [1] in the block of code, not just at the top of the block, which can help with the “not initialized error”. And as a C programmer, your pattern is a nice one. I might have to use that.

                [1] Almost. You can declare variables of the same type in a for()

                for (size_t cnt = 0 ; cnt < max ; cnt++) ...
                

                but not in a while() statment:

                while(int x … ) // illegal

              2. 3

                This is helpful. I’ve had to learn C99 for my CUDA kernels, and the only C reference book I have was published way before that standard (Harbison & Steele 3e, 1991, must be C89). I figured it would be close enough, but I’ve been surprised at some of the things that work and some things that don’t work (my kernels are invoked from C++ code).

                1. 3

                  Great article, both as C and C++ lover.

                  I’d like to point out that another common RAII handled resource deallocations are mutex guards and such.

                  I’ll read up about the mentioned tagged index handle

                  1. 2

                    I really like these “X for Y”-type articles, because it’s a lot faster to associate concepts you already know from one language to another, instead of having to “learn” them all over again, in a generic introduction. They have to be well written though, with plenty of warnings, as to not create false associations that end up being misused.

                    1. 2

                      The updates in the article contain most most of what I would comment, except perhaps that the fixed width integer types are optional (present if the implementation provides them).