One variant that I think might work even better than RAII or defer in a lot of languages is having a thread local "context" which you attach all cleanup actions to. It even works in C, you just define cleanup as a list of
typedef void(cleanup_function*)(void* context);
which is saved as into a thread local. Unlike RAII, you don't need to create a custom type for every cleanup action and unlike the call-with pattern from functional programming, lifetimes of these lists can be non-hierarchical.
However, I'm still glad to see defer being considered for C. It's a lot better than using goto for cleanup.
But that's already what linters/static analyzers are doing? But then, why not integrate those tools directly in a C++ compiler instead?
With cpp2/cppfront, Herb Sutter is already building some sort of a "sane" subset of the C++ language, maybe because you cannot achieve good practices without having a new syntax.
C++ seems to have the same problem of javascript: it has annoying "don't-do-that" use cases, although it seems insanely more complicated to teach good C++ practices.
This sounds like a great idea to me! Rust disables implicit copying for structs with destructors, and together with move-by-default, it works really well. Unlike PoD structs, you don't need to heap allocate them to ensure their uniqueness. Unlike copy constructors, you don't need to worry about implicit copies. Unlike C++ move, there's no moved-from junk value left behind.
The initial example in the article is anti-idiomatic, because it imbues the larger class with a RAIIness which can be limited to just one element of it:
struct ObjectType {
int a;
double b;
void* c;
ObjectType() : a(1), b(2.2), c(malloc(30)) { }
~ObjectType() { free(c); }
};
It's only the c member that really requires any special attention. In this particular case. So, there should be something like a `class void_buffer` which is a RAII class, and then: struct ObjectType {
int a;
double b;
void_buffer c;
ObjectType() : a(1), b(2.2), c(30) { }
};
and actually, let's just not sully the set of constructors, but rather have: struct ObjectType {
int a;
double b;
void_buffer c;
static ObjectType make() {
return ObjectType{ 1, 2.2, 30 };
}
};
and now instead of a complicated bespoke class we have the simplest of structs; the only complexity is in void_buffer.
I haven’t seen this distinction laid out so clearly before:
Every other language worth being so much as spit on either employs deep garbage collection (Go, D, Java, Lua, C#, etc.) or automatic reference counting (Objective-C, Objective-C++, Swift, etc.), uses RAII (Rust with Drop, C++, etc.), or does absolutely nothing while saying to Go Fuck Yourself™ and kicking the developer in the shins for good measure (C, etc.).
GC, ARC, RAII or GTFO, those are the options. That’s right!
I always come away from these discussions with more respect for Objective-C -- such a powerful yet simple language. I suppose Swift is the successor but it feels very different.
Although, Obj-C only really came into its own once it finally gained automatic reference counting, after briefly flirting with GC. At that point it was already being displaced by younger and more fashionable languages.
C11 provided a few worthwhile improvements (i.e., a proper memory model, alignment specification, standardized anonymous structures/unions), but so many of the other additions, suggestions, and proposals I’ve seen will just ruin the minimal nature of C. In C++, a simple statement like `a = b++;` can mean multiple constructors being called, hidden allocations, unexpected exceptions, unclear object hierarchies, an overloaded `++`, an overloaded `=`, etc. Every time I wish I had some C++ feature in C, I just think about the cognitive overhead it’d bring with it, slap myself a couple times, and go back to loving simple ole C.
Please don’t ruin C.
If you miss a destructor event, without configuring the addon "yes I really meant that", the addon halts the compilatoin at best, or returns nonzero for ci at worst.
It's a bit confusing to have a 'thing' mention one mechanism in its name, but actually being valuable by ensuring some other mechanism
You are proposing to change the C language. The risk is great even the smallest change will break the existing code. If you can't convince all of the stakeholders, it's better not to change it. Keep the status-quo.
Oh man, I hear ya. And in a lot more domains than computer language design. Is it inexperience? Impatience? The tendency for search results to be filled with low-quality and high-recency content? The prioritization of hot-take blog posts and Reddit comments over books?
there is dedicated mechanism to achieve RAII-likeness in .NET: try-finally construct
... actually writing code that gets the job done ... in C++.
You can add `defer` instead, but regardless, this has nothing to do with C++. You can implement safety features without having to copy the arguably worst language in the world, C++. I like C++, I wrote many larger projects in it, but it sucks to the very core. Just add RAII to C.
{
void *buffer = malloc(SIZE_MAX);
if (buffer) {
if (!do_stuff(buffer)) {
free(buffer);
return;
}
more_stuff(buffer);
free(buffer);
}
return;
}
If you wanted something like that in C it doesn't need to emulate C++ style RAII with classes and strongly typed constructors. It could look like something like, for example, where you just define pairs of allocator and free functions: allocdef void *autobuffer(malloc, free);
...
{
autobuffer buffer(SIZE_MAX);
if (buffer) {
if (do_stuff(buffer)) {
return;
}
more_stuff(buffer);
}
return;
}
The implementation would effectively be a Lisp style macro expansion encoded in the C compiler (or preprocessor) that would just basically write out the equivalent of the first listing above.
Of course, but if you bothered at all to understand the constraints, you would have seen it is not actually that simple in our case.
And my project was several orders of magnitude simpler than the C standard.