5 ms·
You can have RAII with GCC extensions in C: char *buf __attribute__ ((__cleanup__(free_buf))) = malloc(1024); This calls free_buf(buf) when buf goes out of sc
by stabbles 4y ago
You can have RAII with GCC extensions in C:
char *buf __attribute__ ((__cleanup__(free_buf))) = malloc(1024);
This calls free_buf(buf) when buf goes out of scope.
I’ve actually seen projects use it in the wild to close files or free memory in branchy functions. [1]
[1] https://github.com/containers/bubblewrap/blob/c54bbc6d7b78e7a45016efe0c4ac8af9d209aa20/utils.h#L165 https://github.com/containers/bubblewrap/blob/c54bbc6d7b78e7...
- moloch-hai 4y agoThat is only a small fraction of RAII. The trivial fraction. If malloc fails, real RAII gives you an exception, which you may catch somewhere convenient. Real RAII runs a constructor. If construction fails, you get an exception. Lacking those, it all has to be done and checked by hand on the spot, and is often wrong, because not tested.
- JonChesterfield 4y agoNot sure that's the part of C++ I'd heap praise on. For one thing, real RAII invokes the destructor, not a constructor. If your constructor throws, the destructor isn't going to be called. If your destructor throws, you're one passing exception away from std::terminate. Plus malloc doesn't throw, though new sometimes does. Or, construction could return an optional/maybe style thing that you branch on, and then you don't need goto (sorry, non-local come-from with pretty branding) in your base language.
- mgaunard 4y agoThe important part of RAII is that the constructor actually acquires the resource. The fact the OP insisted the important part of RAII is not that but running destructors shows he hasn't understood the most important concept in C++ programming.
- svvayy 4y agoThe destructor is the distinguishing feature of RAII. Lots of languages have constructors without RAII because they do not have deterministic destructors. Examples are Python and Java. Can you expound on what your understanding is?
- moloch-hai 4y agoThe destructor is what distinguishes C++ from everything before and all but Rust even after, but is, as already explained above, just one part. No exceptions, no RAII.
- stabbles 4y agoThe downside of C++ is that exceptions can come from any possible function call depth, whereas in C you only have to deal with error return codes. So in that sense it’s easier to deal with errors in C, and harder to test the error paths in C++
- moloch-hai 4y agoThat is the upside. You don't need to know how far down it happened. If you need to know more, you catch it lower. But most often you don't. Whatever the hell it was didn't work, and you do whatever is called for, then.
- colejohnson66 4y agoExactly. Being forced to handle errors at every function call sounds great, but then you end being forced to bubble up errors you don't care about like Go does: res, err := func() if err != nil: return nil, err That's not actually handling the error, and exceptions save you from being forced to pretend like you are. Just let it bubble up into the catch block further up the stack.
- moloch-hai 4y agoOr Google dialect. Rust, anyway, captures this foolishness in a macro, which saves lines of source code, but still costs cache footprint, branch predictor slots, and runtime.
- tialaramex 4y agoThe Rust try! macro, which I assume is what you're thinking of, has been obsolete for many years - you can still refer to it because the Rust compatibility promise is taken more seriously than in C++ but you will need to use rather awkward syntax to get at it in modern Rust editions since the keyword "try" is reserved since 2018 edition. These days you'd use the Try operator ? which is not a macro, it's an operator. Try is really interesting, it's a unary operator so it takes a single parameter and it maps that parameter into a control flow decision. For Result the effect is similar to what you got out of the try! macro, but of course this operator can be implemented on any type. The standard library provides six implementations, including famously on Option, but also on ControlFlow itself, which is pretty nice. This means across a complex system you can choose to collect Results, and decide what to do about the Results later, (perhaps after you have all of them, or after you've a certain amount) or you can choose the same for the ControlFlow decisions resulting from those Results. You can also turn things on their heads, and decide that what you want to do is return early on success, but continue processing for errors -- which is something that's just unthinkable in an exception world where control flow and success are somehow the same thing. Rust took some years to figure out that's just not true which would be embarrassing if the assumption that it's true wasn't baked into the entire C++ language.
- RcouF1uZ4gsC 4y agoApologies to Greenspun: “Any sufficiently complicated C contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of C++.” See this hack as well as macros emulating templates for generic data structures and roll your own vtables with function pointers and _Generic for function overloading and function prefixes for namespaces.
- BenFrantzDale 4y agoAs an avid long-time professional C++ user, I agree with that paraphrase. At the same time, I know that (somehow!) people use and like C. I’ve been looking for an article like this explaining how such a to-me-antiquated-feeling language (C) can actually be used and liked. This gave me a glimmer of understanding. I’d love to see more articles like this.
- bluecalm 4y agoFreeing stuff allocated in the same scope is both easy to do and easy to catch when debugging. If you have multiple returns just use goto FINALLY instead of returns and put the deallocations before the return there.
- WalterBright 4y agoC: char *buf __attribute__ ((__cleanup__(free_buf))) = malloc(1024); D: char* buf = cast(char*)malloc(1024); scope (exit) free(buf); https://dlang.org/spec/statement.html#scope-guard-statement https://dlang.org/spec/statement.html#scope-guard-statement The rationale for it: https://dlang.org/articles/exception-safe.html https://dlang.org/articles/exception-safe.html The idea for this came from Andrei Alexandrescu and Petru Marginean who proposed it for C++: http://drdobbs.com/184403758 http://drdobbs.com/184403758
- kllrnohj 4y agoThat's by far the least interesting usage of RAII and also, annoyingly, the only one the article discusses. The better usage by far is describing ownership. Does a function that takes a pointer take ownership of the pointer? Who knows! It's a mystery! Does a function that takes a std::unique_ptr take ownership of the pointer? You're goddamn right it does. Now do the same with things like file descriptors. Managing FDs in Linux is nightmare difficulty because if you get it wrong there's almost never a crash or segfault to tell you about it. Valgrind won't help you find it. Nothing helps you, you're entirely on your own. In Rust the compiler validates ownership for you, trivially made robust. In C++ you can make a "unique_fd" or similar, and at least make accidental mistakes harder. In C? Idk, apparently according to this article that's just a "many small allocations" and you're just a shitty programmer for doing that (wow, such useful advice lol)
- ndesaulniers 4y agoMy biggest annoyance with the design of this extension is that it must be applied explicitly to every instance of the given type. In C++, you get RAII destructor tied to the type, not the instance. With this GNU C extension, you must remember to use it, which IMO is error prone.
- AlbertoGP 4y agoYes, that is one way. I list this and others in the documentation for my own implementation: https://sentido-labs.com/en/library/cedro/202106171400/#deferred-resource-release-related-work https://sentido-labs.com/en/library/cedro/202106171400/#defe...