5 ms·
A long overdue feature. Though I do wonder what the chances are that the C subset of C++ will ever add this feature. I use my own homespun "scope exit" which
by LexiMax 7mo ago
A long overdue feature.
Though I do wonder what the chances are that the C subset of C++ will ever add this feature. I use my own homespun "scope exit" which runs a lambda in a destructor quite a bit, but every time I use it I wish I could just "defer" instead.
- surajrmal 7mo agoIn many cases that's preferred as you want the ability to cancel the deferred lambda.
- anilakar 7mo agoVarious macro tricks have existed for a long time but nobody has been able to wrap the return statement yet. The lack of RAII-style automatic cleanups was one of the root causes for the legendary goto fail;[1] bug. [1] https://gotofail.com/ https://gotofail.com/
- uecker 7mo agoI do not see how defer would have helped in this case.
- Davidbrcz 7mo agoPeople manually doing resource cleanup by using goto. I'm assuming that using defer would have prevented the gotos in the first case, and the bug.
- mort96 7mo agoIs that true though? Using defer, the code would be: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) return err; return err; This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' fixes nothing.
- anilakar 7mo agoIt would have resulted in an uninitialized variable access warning, though.
- mort96 7mo agoNo it wouldn't. 'err' is declared and initialized at the start of the function. Even if it wasn't initialized at the start, it would've been initialized by some earlier fallible function call which is also written as 'if ((err = something()) != 0)'
- uecker 7mo agoI don't think so. The value is set in the assignment in the if statement even for the success path. With and without defer you nowadays get only a warning due to the misleading indentation: https://godbolt.org/z/3G4jzrTTr https://godbolt.org/z/3G4jzrTTr (updated)
- uecker 7mo agoI don't see this. The problem was a duplicate "goto fail" statement where the second one caused an incorrect return value to be returned. A duplicate defer statement could directly cause a double free. A duplicate "return err;" statement would have the same problem as the "goto fail" code. Potentially, a defer based solution could eliminate the variable for the return code, but this is not the only way to address this problem.
- anilakar 7mo agoTo be fair, there were multiple wrongs in that piece of code: avoiding typing with the forward goto cleanup pattern; not using braces; not using autoformatting that would have popped out that second goto statement; ignoring compiler warnings and IDE coloring of dead code or not having those warnings enabled in the first place. C is hard enough as is to get right and every tool and development pattern that helps avoid common pitfalls is welcome.
- mort96 7mo agoThe forward goto cleanup pattern is not something "wrong" that was done to "avoid typing". Goto cleanup is the only reasonable way I know to semi-reliably clean up resources in C, and is widely used among most of the large C code bases out there. It's the main way resource cleanup is done in Linux. By putting all the cleanup code at the end of the function after a cleanup label, you have reduced the complexity of resource management: you have one place where the resource is acquired, and one place where the resource is freed. This is actually manageable. Before you return, you check every resource you might have acquired, and if your handle (pointer, file descriptor, PID, whatever) is not in its null state (null pointer, -1, whatever), you call the free function. By comparison, if you try to put the correct cleanup functions at every exit point, the problem explodes in complexity. Whereas correctly adding a new resource using the 'goto cleanup' pattern requires adding a single 'if (my_resource is not its null value) { cleanup(my_resource) }' at the end of the function, correctly adding a new resource using the 'cleanup at every exit point' pattern requires going through every single exit point in the function, considering whether or not the resource will be acquired at that time, and if it is, adding the cleanup code. Adding a new exit point similarly requires going through all resources used by the function and determining which ones need to be cleaned up. C is hard enough as it is to get right when you only need to remember to clean up resources in one place. It gets infinitely harder when you need to match up cleanup code with returns.
- mananaysiempre 7mo agoIn theory, for straight-line code only, the If Statement Ladder of Doom is an alternative: int ret; FILE *fp; if ((fp = fopen("hello.txt", "w")) == NULL) { perror("fopen"); ret = -1; } else { const char message[] = "hello world\n"; if (fwrite(message, 1, sizeof message - 1, fp) != sizeof message - 1) { perror("fwrite"); ret = -1; } else { ret = 0; } /* fallible cleanup is unpleasant: */ if (fclose(fp) < 0) { perror("fclose"); ret = -1; } } return ret; It is in particular universal in Microsoft documentation (but notably not actual Microsoft code; e.g. https://github.com/dotnet/runtime https://github.com/dotnet/runtime has plenty of cleanup gotos). In practice, well, the “of doom” part applies: two fallible functions on the main path is (I think) about as many as you can use it for and still have the code look reasonable. A well-known unreasonable example is the official usage sample for IFileDialog: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog#basic-usage https://learn.microsoft.com/en-us/windows/win32/shell/common....
- pjmlp 7mo agoNever, you can already do this with RAII, and naturally it would be yet another thing to complain about C++ adding features. Then again, if someone is willing to push it through WG21 no matter what, maybe.
- mort96 7mo agoC++ implementations of defer are either really ugly thanks to using lambdas and explicitly named variables which only exist to have scoped object, or they depend on macros which need to have either a long manually namespaced name or you risk stepping on the toes of a library. I had to rename my defer macro from DEFER to MYPOROGRAM_DEFER in a project due to a macro collision. 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.
- pjmlp 7mo agoBecause they are all the consequence of holding it wrong, avoiding RAII solutions. 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.
- mort96 7mo agoIf I'm gonna write RAII wrappers around every tiny little thing that I happen to need to call once... I might as well just use Rust and make the wrappers do FFI. 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.
- feelamee 7mo agoif you do it once - why do you care about "ugly" scope_exit? btw, writing such wrappers is easy and does not require a lot of code.
- mwkaufma 7mo agoJust hope those lambdas aren't throwing exceptions ;)