The more we use RAII in C++, the more we find ourselves with destructors that do non-trivial deallocation. Now, deallocation (finalization, however you want to call it) can fail
You're looking at two things:
RAII promises that it will complete the operation (free memory, close the file having attempted to flush it, end a transaction having attempted to commit it). But because it happens automatically, without the programmer having to do anything, it doesn't tell the programmer whether those operations it "attempted" succeeded or not.
Exceptions are one way to report that something failed, but as you say, there's a limitation of the C++ language that means they aren't suitable to do that from a destructor[*]. Return values are another way, but it's even more obvious that destructors can't use those either.
So, if you want to know whether your data was written to disk, you can't use RAII for that. It does not "defeat the whole purpose of RAII", since RAII will still try to write it, and it will still release the resources associated with the file handle (DB transaction, whatever). It does limit what RAII can do -- it won't tell you whether the data was written or not, so for that you need a close()
function that can return a value and/or throw an exception.
[*] It's quite a natural limitation too, present in other languages. If you think RAII destructors should throw exceptions to say "something has gone wrong!", then something has to happen when there's already an exception in flight, that is "something else has gone wrong even before that!". Languages that I know that use exceptions don't permit two exceptions in flight at once - the language and syntax simply don't allow for it. If RAII is to do what you want, then exceptions themselves need to be redefined so that it makes sense for one thread to have more than one thing going wrong at a time, and for two exceptions to propagate outward and two handlers to be called, one to handle each.
Other languages allow the second exception to obscure the first, for example if a finally
block throws in Java. C++ pretty much says that the second one must be suppressed, otherwise terminate
is called (suppressing both, in a sense). In neither case are the higher stack levels informed of both faults. What is a bit unfortunate is that in C++ you can't reliably tell whether one more exception is one too many (uncaught_exception
doesn't tell you that, it tells you something different), so you can't even throw in the case where there isn't already an exception in flight. But even if you could do it in that case, you'd still be stuffed in the case where one more is one too many.