C++ would be a nicer language with native defer. Working directly with C APIs (which is one of the main reasons to use C++ over Rust or Zig these days) would greatly benefit from it.
C++ would be a nicer language with native defer. Working directly with C APIs (which is one of the main reasons to use C++ over Rust or Zig these days) would greatly benefit from it.
Working with native C APIs in C++ is akin to using unsafe in Rust, C#, Swift..., it should be wrapped in type safe functions or classes/structs, never used directly outside implementation code.
If folks actually followed this more often, there would be so much less CVE reports in C++ code caused by calling into C.
If I'm constructing a particular C object once in my entire code base, calling a couple functions on it, then freeing it, I'm not much more likely to get it right in the RAII wrapper than in the one place in my code base I do it manually. At least if I have tools like defer to help me.
Do you mean why I care that I have to call the free function at every exit point of the scope? That's easy: because it's error prone. Defer is much less error prone.
I agree with @pjmlp - you need to write wrappers around the C api.
But if you.. > If I'm gonna write RAII wrappers around every tiny little thing that I happen to need to call once
use them just once.. so, why care about ugliness, just write ugly code just once? Code can't be perfect.
The reason why C++ is as popular as it is is in large part due to how easy it is to upgrade an existing C codebase in-place. Doing a complete RAII rewrite is at best a long term objective, if not often completely out of the question.
Acknowledging this reality means giving affordances like `defer` that allow upgrading C codebases and C++ code written in a C style easier without having to rewrite the universe. Because if you're asking me to rewrite code in a C++ style all in one go, I might not pick C++.
EDIT: It also occurs to me that destructors also have limitations. They can't throw, which means that if you encounter an issue in a dtor you often have to ignore it and hope it wasn't important.
I ran into this particular annoyance when I was writing my own stream abstractions - I had to hope that closing the stream in the dtor didn't run into trouble.
Yes, unfortunely that compatibility is also the Achilles hill of C++, so many C++ libraries that are plain old C libraries with extern "C { .... } added in when using a C++ compiler, and also why so many CVEs keep happening in C++ code.
struct Example {
Example() = default;
~Example()
try {
// elease resources for this instance
} catch (...) {
// take care of what went wrong in the whole destructor call chain
}
};
-- https://cpp.godbolt.org/z/55oMarbqYNow with C++26 reflection, one could eventually generate such boilerplate.
The mechanism in Java I was alluding to is really the Throwable::addSuppressed method; it isn’t tied to the use of a try-block. Since Java doesn’t have destructors, it’s just that the try-with-resources statement is the canonical example of taking advantage of that mechanism.