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.

Never, 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.

C++ 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.

Because 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.

> Because they are all the consequence of holding it wrong, avoiding RAII solutions.

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.

You can use a function try block on the destructor, additionally thanks to C++ metaprogramming capabilities, many of these handler classes can be written only once and reused across multiple scenarios.

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.

If 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.

if 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.

What do you mean with '"ugly" scope_exit'?

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.

your words... > C++ implementations of defer are either really ugly

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.

Not to mention that the `scope_success` and `scope_failure` variants have to use `std::uncaught_exceptions()`, which is hostile to codegen and also has other problems, especially in coroutines. C++ could get exception-aware variants of language defer.

What C++ really needs is an automated way to handle exceptions in destructors, similar to how Java does in its try-with-resources finally blocks.

While not automated, you can make use of function-try-blocks, e.g.:

    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/55oMarbqY

Now with C++26 reflection, one could eventually generate such boilerplate.

What I’m thinking of is that the C++ exception runtime would attach exceptions from destructors to any in-flight exception, forming an exception tree, instead of calling std::terminate. (And also provide an API to access that tree.) C++ already has to handle a potentially unlimited amount of simultaneous in-flight exceptions (nested destructor calls), so from a resource perspective having such a tree isn’t a completely new quality. In case of resource exhaustion, the latest exception to be attached can be replaced by a statically allocated resources_exhausted exception. Callbacks like the old std::unexpected could be added to customize the behavior.

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.

I see, however I don't envision many folks on WG21 votting that in.

Various 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/

I do not see how defer would have helped in this case.

People 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.

To 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.

The 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.

In 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 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....

[deleted]

I 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.

Is 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.

It would have resulted in an uninitialized variable access warning, though.

I 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 (updated)

No 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)'

In many cases that's preferred as you want the ability to cancel the deferred lambda.

Just hope those lambdas aren't throwing exceptions ;)