3 ms·
Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks. using (var
by sparkie 8mo ago
Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks.
using (var resource = acquire()) {
} // implicit resource.Dispose();
While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like:
using (auto resource = acquire(); free(resource)) {
} // free(resource) call inserted here.
That's no so different to how a `for` block works:
for (auto it = 0; it < count; it++) {
} // automatically inserts it++; it < count; and conditional branch after secondary block of for loop.
A trivial "hack" for this kind of scoped defer would be to just wrap a for loop in a macro:
#define using(var, acquire, release) \
auto var = (acquire); \
for (bool var##_once = true; var##_once; var##_once = false, (release))
using (foo, malloc(szfoo), free(foo)) {
using (bar, malloc(szbar), free(bar)) {
...
} // free(bar) gets called here.
} // free(foo) gets called here.
- usefulcat 8mo agoThat is a different approach, but I don't think you've demonstrated why it's better. Seems like that approach forces you to introduce a new scope for every resource, which might otherwise be unnecessary: using (var resource1 = acquire() { using (var resource2 = acquire()) { using (var resource3 = acquire()) { // use resources here.. } } } Compared to: var resource1 = acquire(); defer { release(resource1); } var resource2 = acquire(); defer { release(resource2); } var resource3 = acquire(); defer { release(resource3); } // use resources here Of course if you want the extra scopes (for whatever reason), you can still do that with defer, you're just not forced to.
- sparkie 8mo agoWhile the macro version doesn't permit this, if it were built-in syntax (as in C#) we can write something like: using (auto res1 = acquire1(); free(res1)) using (auto res2 = acquire2(); free(res2)) using (auto res3 = acquire3(); free(res3)) { // use resources here } // free(res3); free(res2); free(res1); called in that order. The argument for this approach is it is structural. `defer` statements are not structural control flow: They're `goto` or `comefrom` in disguise. --- Even if we didn't want to introduce new scope, we could have something like F#'s `use`[1], which makes the resource available until the end of the scope it was introduced. use auto res1 = acquire1() defer { free(res1); }; use auto res2 = acquire2() defer { free(res2); }; use auto res3 = acquire3() defer { free(res3); }; // use resources here In either case (using or use-defer), the acquisition and release are coupled together in the code. With `defer` statements they're scattered as separate statements. The main argument for `defer` is to keep the acquisition and release of resources together in code, but defer statements fail at doing that. [1]:https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/resource-management-the-use-keyword#use-binding https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...