7 ms·
The author should take another look at RIIA. The first big difference is that it works with non-memory resources too, for example open/close for files. The se
by DougBTX 4y ago
The author should take another look at RIIA.
The first big difference is that it works with non-memory resources too, for example open/close for files.
The second is that it makes heap allocated data feel a lot like stack allocated data, since the resources are ultimately owned by variables on the stack. With “move semantics” resources can be passed as arguments and returned by functions too, so the example of lifetimes crossing calls is fine. A function could take in a File, read some data, use that to select and open a different File, then return the new file and close the old file at the end of the call, and it would all be straightforward.
This is all present in C++. If it seems like an interesting idea to learn more about, I recommend trying it in Rust. It is the default, so there’s minimal syntax for it and all the standard library uses it.
- GoblinSlayer 4y agoIt's a C++ myth that files and memory are the same problem, in reality files are an easier problem, cf java.
- saurik 4y ago?! Java doesn't offer a reasonable solution for files... it is presumed they can be closed by the garbage collector calling "finalizers", but that results in problems such as holding file locks long after they should have been released (which is a correctness problem) and running out of file handles (a limited resource that Java does not track pressure against) due to this mechanism having no time horizon. The result is that you are essentially trapped into the realm of manual resource management, having to put a try block around every single use of a file to make sure you can control its lifetime and prevent it from "leaking" into the collector. You then would have to use a finally block and call close. Memory is certainly the easier problem as it is a single constrained resource with usage semantics (licenses and conflicts) entirely constrained by the language. (They at least have recently added some syntax that makes the last bit of that easier, but which doesn't at all make it less manual. This syntax is somewhat based on C#'s using blocks, a feature which I might have actually caused due to some strong advocacy surrounding this issue when .NET was in beta, and yet I have always insisted this syntax failed to correctly understand the problem as, even if you buy into the manual-ness of it all, it leads to colored objects, as there are objects controlled by the garbage collector and objects controlled by using and now adding a field to an object that must be disposed flips it from one regime into the other regime without causing a type incompatibility.)
- GoblinSlayer 4y agoThere's no limit to perfection, but java approach is reasonable, because it works in practice.
- cryptonector 4y agoIf you use a try-with-resources in Java, then the closeables are closed immediately when the try block exits, and in reverse order to acquisition.
- vitiral 4y agoThere is a separate solution for files: defer. Defer can be implemented trivially in assembly by putting the deferred block on the return stack (then the defer block simply returns). This glosses over handling the local stack, but it's not impossible. It would be a relatively minor feature to add to C IMO
- kevin_thibedeau 4y agoBreaks with longjmp().
- vitiral 4y agoI've not done an in-depth analysis but I don't think longjmp is as performant as pushing (and updating) the return stack. Maybe you could do it with assign to our variable + jmp.
- jcelerier 4y agocf Java? Both in it and c# forgetting to close handles has been one of the most common mistakes I've seen. It pretty much never happens on c++.
- GoblinSlayer 4y agoI saw it two times in real world C++ code and never saw it in C# and java. Memory leaks are much more common.
- bunderbunder 4y agoI would argue that the opposite is true, because memory is fungible and files are not. c.f.) Java, where the GC can handle memory, but it didn’t work out so well for files, so, to restore correctness, we needed the Closeable and AutoCloseable interfaces to give manual control back to the programmers.
- yakubin 4y agoClosing a file doesn't trigger a cascade of closing other files, while freeing memory in RAII does trigger a cascade of more frees.
- TomSwirly 4y agoNo, C++ has the idea that the same solution can be used for all resource management: file handles, memory, sockets, database cursors or connections, you name it. And it works. Resource life management is not a problem that Java handles with grace...
- GoblinSlayer 4y agoThat's the myth, C++ way is not the only true way. Many C++ programmers know only C++ way and can't imagine anything else, because they spent all their life on learning C++ and it consumed all their mental resources.
- flohofwoe 4y agoIMHO the problem with C++ style RIIA is that it's not just about calling cleanup code at the end of a scope (this would be fine, really), but the 'rat's tail' that's attached to RAII for dynamic memory management: you also need the compiler to be able to invoke user-provided code for 'copy' and 'move' operations (or better named: deep- and flat-copy), you need different reference types to define ownership details (e.g. unique vs shared) etc... this adds a lot of bells and whistles to the language which is only needed in special situations (such as tracking the individual lifetimes of tons of tiny heap objects, which usually isn't a good idea in the first place).
- TomSwirly 4y ago> (or better named: deep- and flat-copy) That's worse naming, not better! In particular, a copy does not change the thing it copies - but a C++ move does change the thing it moves from.
- flohofwoe 4y agoOk "flat-copy-with-src-invalidation". Somehow "move" makes it sound more efficient than copying a C struct, but it's actually the same thing (for POD structs), for non-POD it's even slightly more expensive because you need to fix up the source.
- maccard 4y agoI'm definitely not helping this conversation but: > "flat-copy-with-src-invalidation". That's not what a move is in C++, at all. I wish we got destructive moves in C++, but we didn't (and I understand why..) > for non-POD it's even slightly more expensive because you need to fix up the source. This is just false.
- flohofwoe 4y agoHuh, you really need to elaborate on both points. As far as I remember from my 'active' C++ time, a move operator does a 'flat copy' of all 'top level items' in an object, and then puts the source object into a state where the destructor can safely be called on it without destroying any of the previously owned resources (which means for instance setting any owning pointers to zero so that the destructor doesn't free the memory that was owned by those pointers, because ownership of this memory has moved to the destination object). It gets a bit more non-obvious if any of those items are complex C++ objects themselves, but in the end it's always "copy all the bits, and then clear any owning pointers without freeing the underlying memory". Did I miss something?
- omginternets 4y agoCould someone please explain the usefulness of RAII? As I understand it, RAII refers to the initialization semantics of certain languages, most famously C++. If I remember correctly, it effectively states that variable declarations amount to reserving memory resources and nothing else; memory is not zeroed, for instance. My question is: what makes this so useful? Looking at wikipedia, the focus seems to be on lifetime guarantees, but I'm having trouble understanding how these are useful in practice. Contrasting this with a non-RAII language like Go might be helpful.
- 8organicbits 4y agoGo has defer, so you can write: f = os.Open(...) defer f.Close() // do stuff with f... This ensures you close the file at the end of the scope. If processing the file is complex, with lots of places where return is called, then there's lots of places where the Close call could actually need happen. Thankfully you just write it once with defer, and you can forget about it. In languages without defer you need something else. The RIIA approach is to create a file object where the constructor opens the file and the destructor closes the file. The compiler needs to track the file objects scope in order to call the destructor. This acts as a hook to run some code right at the end of the scope, wherever that actually occurs. Both call close right before returning, but the actual mechanism is different because the languages have different tools.
- xjay 4y ago"RAII" has some overlapping associations. Primary purpose: To guarantee the cleanup of resources during stack unwinding, after an exception is thrown, because it's tightly bound to the automatic storage duration mechanism (the static code generated to manage the stack at runtime). Secondary purposes: 1) To immediately initialize some resource handler where it is declared. 2) To automate the cleanup of some resource when execution leaves the scope in which it was declared, also hiding explicit calls to Close, Free, Unlock, Release, etc. To restate the source, RAII was the premise for exceptions. [1] [1] C++ in Constrained Environments - Bjarne Stroustrup - CppCon 2022 https://www.youtube.com/watch?v=2BuJjaGuInI&t=4000s https://www.youtube.com/watch?v=2BuJjaGuInI&t=4000s
- usefulcat 4y ago