December 22nd, 2025
intriguinglikeheartmind blown9 reactions

All the other cool languages have tryfinally. C++ says “We have tryfinally at home.”

Many languages¹ that have exceptions also have a finally clause, so you can write

try {
    ⟦ stuff ⟧
} finally {
    always();
}

A quick checks shows that this control structure exists in Java, C#, Python, JavaScript, but not C++.

C++ says, “We have try…finally at home.”

In C++, the way to get a block of code to execute when control leaves a block is to put it in a destructor, because destructors run when control leaves a block. This is the trick used by the Windows Implementation Library’s wil::scope_exit function: The lambda you provide is placed inside a custom object whose destructor runs the lambda.

auto ensure_cleanup = wil::scope_exit([&] { always(); });

⟦ stuff ⟧

Although the principle is the same, there are some quirks in how each language treats the case where the finally or destructor itself throws an exception.

If control leaves the guarded block without an exception, then any uncaught exception that occurs in the finally block or the destructor is thrown from the try block. All the languages seem to agree on this.

If control leaves the guarded block with an exception, and the finally block or destructor also throws an exception, then the behavior varies by language.

  • In Java, Python, JavaScript, and C# an exception thrown from a finally block overwrites the original exception, and the original exception is lost. Update: Adam Rosenfield points out that Python 3.2 now saves the original exception as the context of the new exception, but it is still the new exception that is thrown.
  • In C++, an exception thrown from a destructor triggers automatic program termination if the destructor is running due to an exception.²

So C++ gives you the ability to run code when control leaves a scope, but your code had better not allow an exception to escape if you know what’s good for you.

¹ The Microsoft compiler also supports the __try and __finally keywords for structured exception handling. These are, however, intended for C code. Don’t use them in C++ code because they interact with C++ exceptions in sometimes-confusing ways.

² This is why wil::scope_exit documents that it will terminate the process if the lambda throws an exception. There is an alternate function wil::scope_exit_log that logs and then ignores exceptions that are thrown from the lambda. There is no variation that gives you Java-like behavior.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

27 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • Dave Brosius · Edited

    In Java you can avoid a finally exception from hiding a try exception, by moving the finally code to a try-with resources AutoCloseable. In this way if the try concludes normally and the exception is thrown by the autocloseable, then the autocloseable’s exception is the thing that’s thrown. If on the other hand the try throws an exception, the auto closeable’s exception is merely attached to the original exception as extra info. See Throwable.getSuppressed.

  • Ryan Johnson

    You’re throwing good money after bad. Ditch the problem system early and often.

  • Pejman Pakrooh

    It is correct, but only in not-very-common try-finally blocks and not in try-except-finally ones. In latter case we should store the first exception in a variable and then raise from it in finally

    • Kevin Norris

      If the exception is caught by the except and not re-raised, then by the time the finally clause executes, there’s no longer a “current exception.” We already caught and handled it. Chaining wouldn’t make sense, because the finally block did not replace an exception, it just raised a new one after we already disposed of the old one.

      If the exception is re-raised in the except clause, then it chains just like in try-finally.

  • Joshua Hudson

    (reply to Kevin Norris in case thread mode breaks again)

    You don’t need try/finally for that. You need try { } catch { throw; } . That solves that problem quite well indeed.

  • Ryan Johnson · Edited

    So start using D lang. Stop using C and C++. And don’t fall for the popularity of Java or Python. D lang for system language and general programming and Julia for data processing

    • Raymond ChenMicrosoft employee Author

      Of course, the article is written from the point of view of “We have an existing project written in C++ and we want to use something like try/finally because it looks nicer.” If your answer to a small problem is “Throw away your program and rewrite it in another language,” you’re not going to get a lot of takers.

      • Raymond ChenMicrosoft employee Author · Edited

        > I bet your Microsoft colleague Galen Hunt agrees. He has recently made headlines.

        @Flux: And he has already backtracked.

        > Just to clarify... Windows is *NOT* being rewritten in Rust with AI.

        > My team’s project is a research project. We are building tech to make migration from language to language possible. The intent of my post was to find like-minded engineers to join us on the next stage of this multi-year endeavor—not to set a new strategy for Windows 11+ or to imply that Rust is an endpoint.

        Read more
      • Flux

        I bet your Microsoft colleague Galen Hunt agrees. He has recently made headlines.

    • LB

      I’ve heard Rust and Ada can do systems programming too, but let me know when game consoles like Switch/Playstation/Xbox have consistent first party support for anything other than C++.

    • anonymous

      this comment has been deleted.

  • Pejman Pakrooh

    Since I cannot reply directly to the author’s comment regarding function-try-blocks, I am posting this follow-up:
    While it’s true the object is not fully constructed if an exception occurs in the constructor, a memory leak can still occur. If the constructor allocates heap memory before the exception is thrown, that memory will never be freed because the destructor is never called. In this situation, we can use function-try-blocks to manually release memory.

    • LB · Edited

      You can delegate to the default constructor (or a private constructor which never fails) if you want the destructor to run, since the destructor will run as long as any constructor finishes normally.

    • Kevin Norris · Edited

      This is avoidable in several ways other than function-try-blocks. Ideally, the whole constructor consists entirely of member initializers with no actual body. Then the fields will be destructed even if the object as a whole is not. If this is infeasible, the next best option is to default-initialize each member in an empty state, construct the proper values in local variables, and then use std::swap() to put the values into the members. That will also ensure that everything is cleaned up properly, at the expense of requiring subobjects to support an empty state. But supporting an empty state has been...

      Read more
      • Pejman Pakrooh

        I completely agree that the issue is avoidable through better design patterns. My comment was specifically addressing the claim that destructors run unconditionally. I was actually highlighting a technical exception to that rule.

  • Pejman Pakrooh · Edited

    Python loses the exception raised in the try block when another exception is raised in the finally block. Automatic exception chaining only occurs if another exception is raised in the except block. this automatic chaining in except can be prevented with raise NewException from None.
    There is a somehow similar, though more manual, mechanism in C++: std::throw_with_nested

    • Kevin Norris

      I just tested it in Python 3.13, and it does in fact chain when an exception is raised in the finally block.

      • Adam Rosenfield

        Seems that the behavior changed between Python 3.1 and 3.2. In Python 3.1:

        If the finally clause raises another exception or executes a return or break statement, the saved exception is lost.

        In Python 3.2:

        If the finally clause raises another exception, the saved exception is set as the context of the new exception.

  • Yexuan Xiao

    C++ doesn’t need using/with/defer either, because destructors run automatically without condition (unless you’re writing containers). This eliminates the risk of memory or handle leaks from forgetting to mark cleanup objects.

    • Pejman Pakrooh

      Unfortunately, your statement is not entirely true. Destructors do not run unconditionally in C++. One of the most common scenarios in which a destructor call is skipped is when an exception is thrown in the constructor or the member initializer list (ctor initializer). There is even a special, though sadly not well-known, syntax for handling this scenario called function-try-block.

      • heto

        Function try blocks hardly help with this. You don’t know which subobjects (base classes and data members) have been constructed and which haven’t in a catch block of a function try block. You should not read those subobjects which have not yet been initialised, so you can’t check the members to find out which ones should be freed and which were not yet initialised. All you can trust is that those subobjects initialised before the first non-noexcept subobject in the initialisation order have been initialised.

        The only reasonable way is to use a separate member with its own destructor for each...

        Read more
      • Yexuan Xiao

        As long as you ensure that the objects initialized in the initializer list have destructors, they will be cleaned up once they are constructed. Function try blocks can indeed catch exceptions from them, but there’s no need to use function try blocks for cleanup.

      • LB

        @Pejman Pakrooh Unfortunately, your statement is also not entirely true. Destructors do run as long as any constructor finishes. This means if you use a delegating constructor and throw from the body of the constructor after the delegated constructor finished, the destructor will run. It’s a useful trick to delegate to the default constructor just to be sure that the destructor will always run.

      • Raymond ChenMicrosoft employee Author

        In that case, the object was never constructed, so talking about the destructor is meaningless.

  • LB

    Worth noting, C++ also makes you explicitly declare the destructor as `noexcept(false)` if you want to allow exceptions to escape it. Also worth noting, if a class X has a base class or data member with a `noexcept(false)` destructor, then the class X destructor is also implicitly `noexcept(false)` too unless it was explicitly declared `noexcept`. Be careful when writing containers or generic types that directly store user-provided types.

  • Kevin Norris

    Nitpick: Python does not “lose” the original exception if a new exception is raised. Instead, it stashes the old exception inside of the new exception, and arranges for the traceback to print both exceptions (or all N exceptions, if the old exception already had another exception stashed inside of it).

    • IS4

      Java too has getSuppressed for this purpose, but as far as I can tell, it only ever uses them automatically for exceptions raised from the try-with-resources statement.

      Rare W for Python here.