Exceptions with mutable state needs to be coded transactionally, yes, and this is an excellent reason to try and avoid mutable state and use persistent data structures where possible.
.. which makes using the STL surprisingly hard, because you can get an out of memory exception all over the place.
In practice, I think this issue gets far more attention than it deserves, particularly within the C++ community. If you’re running in a particularly resource-constrained environment where running out is a relevant problem, you probably already take precautions about how you allocate those resources and adjust how you structure your program accordingly. You’re trying to avoid any possibility of those errors occurring, which means you don’t then have to worry about dealing with them unexpectedly all over your code.
For me, the more interesting discussing is around how we deal with errors that can be properly recognised and handled within normal execution. That means the interesting exceptions are those we throw ourselves and those deliberately thrown by other modules that we depend on, which we hope will be documented with each module’s interface. These things don’t just go off arbitrarily in just about any line of code we write, and they can be systematically structured, and we can write code that controls where mutations and other effects happen and where exceptions happen and how they interact.
This means that writing state-modifying code becomes unreasonably difficult, as you can no longer just write:
.. because it's possible that computeB() throws an exception, leaving the two out of sync.Yes, there are ways round this involving temporary variables. So you write:
and home that neither an optimiser nor an intern removes the tmp variables. But wait! This still doesn't work, because your evil coworker has defined operator= on B to throw an exception!Personally I don't think that exceptions work very well in the presence of mutable state. Without mutable state, they're exactly equivalent to