Next: C++/CLI limitations?
From: Alf P. Steinbach on 9 Jul 2005 04:45 [Addressing just two basic points] * David Abrahams -> Alf P. Steinbach: > > > and third, because there's no (under assumption of valid higher > > level states) guarantee of execution, as there is with RAII. > > Not sure what you had in mind here. With exceptions the client code that needs cleanup doesn't need to take any explicit extra action to have that cleanup performed. That's one less thing that can go wrong in the initial coding, and that's one main reason why exceptions are used for less fatal circumstances. For example, if you create a huge temporary file (as someone else recently used as an example in this thread), then you can guard against exceptions by using an automatic object with a suitable destructor, TempFile f(aLargeSize); and when the code is all exception based that simple declaration is all that's needed. However when cleanup for a fatal error is centralized (to be run at the detection call-chain level) you have to adapt the TempFile class to support that centralized clean-up, or do something extra each place a TempFile is used, to hook it up with the clean-up, or not clean up. Ideally the destructor should be able to differentiate between an ordinary exception (where destruction failure could be handled by throwing a "hard" exception) and a "hard" exception (where it's known the process is going to terminate anyway, no special action except logging needed on failure). But as it is a C++ destructor doesn't even know whether it's invoked via exception-induced stack unwinding or not. As with most other high-level concepts, e.g. modules, where we have to use the preprocessor, the language support isn't there, and the concept must be emulated via conventions. > > And, it's reasonable and common (it even has built-in language support) > > to do recursive clean-up when an object is destroyed. > > What does an object being destroyed have to do with anything? > > > But ensuring destruction of objects can be difficult when the stack > > is not unwound. So with no stack unwinding the objects may have to > > be designed to be able to do total clean-up without being destroyed, > > which effectively means adding zombie states. > > No, you're going to stop the program anyway, which means the objects > die automatically. There's no concept of object death in the standard. ;-) Assuming that you mean, those C++ objects are going to disappear: no more process, no more objects. Yes, and that disappearance nearly without any trace, leaving behind a mess and little or no information about the detailed reasons, is what we're trying to avoid. C++ object destruction is more than internal-to-the-process object disappearance. C++ object destruction is recursive, and often involves external resources. To invoke that recursive destruction with cleaning up of external resources and possibly logging of states, etc., the objects have to be destroyed via the C++ mechanisms, not just disappeared along with the process. But ensuring C++ destruction of objects can be difficult when the stack is not unwound. So with no stack unwinding the objects may have to be designed to be able to do total clean-up without being destroyed, which effectively means adding zombie states. The TempFile object above is a bad example in this respect because a file object typically has a possible zombie state anyway, at least if it's designed to deal with errors. But replace "file" with anything. -- A: Because it messes up the order in which people normally read text. Q: Why is it such a bad thing? A: Top-posting. Q: What is the most annoying thing on usenet and in e-mail? [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Dave Harris on 9 Jul 2005 14:28 gerhard.menzl(a)hotmail.com (Gerhard Menzl) wrote (abridged): > When flight control software encounters a negative altitude value, > it had better shut down (and, hopefully, let the backup system > take over). On the other hand, a word processor that aborts and > destroys tons of unsaved work just because the spellchecker has met a > violated invariant is just inacceptable. I think your real problem there is that you have tons of unsaved work. You can cope with program bugs, maybe, but you probably can't cope with O/S crashes or power cuts so you need a more general strategy. My employers write word processors. Our approach is to write the document to a temporary file every 10 or so minutes. We delete it during a normal shutdown and also after a successful user-save. Any documents found during start-up are then unsaved work lost due to a crash, and we ask the user if she wants them restored. This mitigates the problem. They never lose more than 10 minutes work. That's acceptable because crashes should be rare (if they are a daily occurance we are already in big trouble). We are confident we can restore the document because we had a known sane state when it was written out. -- Dave Harris, Nottingham, UK. [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: David Abrahams on 9 Jul 2005 14:33 alfps(a)start.no (Alf P. Steinbach) writes: > [Addressing just two basic points] > > * David Abrahams -> Alf P. Steinbach: >> >> > and third, because there's no (under assumption of valid higher >> > level states) guarantee of execution, as there is with RAII. >> >> Not sure what you had in mind here. > > With exceptions the client code that needs cleanup doesn't need to take > any explicit extra action to have that cleanup performed. That's one less > thing that can go wrong in the initial coding, and that's one main reason > why exceptions are used for less fatal circumstances. That's true for things that need to be cleaned up "anyway" and are allocated on the stack or managed by stack objects, but that leaves out a lot. For example, your argument doesn't work for the particular case you cited: interactive document editing software. The document is not going to be "cleaned up" in the normal course of events. > For example, if you create a huge temporary file (as someone else > recently used as an example in this thread), then you can guard > against exceptions by using an automatic object with a suitable > destructor, > > TempFile f(aLargeSize); > > and when the code is all exception based that simple declaration is all > that's needed. That's true, but in my opinion temporary files fall into a separate class of resource -- those that need to be cleaned up even in case of fatal errors -- and that class should be dealt with by a separate mechanism. If you are going to try to do something reasonable in the face of programming errors, you probably need to deal with the case where an exception specification is violated, or where an exception gets thrown from a destructor during unwinding, anyway. So these kinds of cleanups should be registered with atexit (and unregistered when the corresponding stack object is destroyed) or in the terminate() handler[1]. > However when cleanup for a fatal error is centralized (to be run at the > detection call-chain level) I still don't understand what you mean by "centralized." > you have to adapt the TempFile class to support that centralized > clean-up, or do something extra each place a TempFile is used, to > hook it up with the clean-up, or not clean up. Yep, exactly. > Ideally the destructor should be able to differentiate between an ordinary > exception (where destruction failure could be handled by throwing a "hard" > exception) and a "hard" exception (where it's known the process is going to > terminate anyway, no special action except logging needed on failure). But > as it is a C++ destructor doesn't even know whether it's invoked via > exception-induced stack unwinding or not. Actually it does. That's what uncaught_exception() tells you. The only problem with uncaught_exception is that it doesn't tell you when you're in a catch(...) { ... throw; } block[2], but it does tell the truth: no stack unwinding is in progress. > As with most other high-level concepts, e.g. modules, where we have > to use the preprocessor, Whoa, preprocessor??! How is that relevant here? > the language support isn't there, and the concept must be emulated > via conventions. You can easily support this with a simple library (in fact I've been meaning to write one for this purpose for years). I wouldn't call using a library "emulation via convention." >> > And, it's reasonable and common (it even has built-in language support) >> > to do recursive clean-up when an object is destroyed. >> >> What does an object being destroyed have to do with anything? ?? >> > But ensuring destruction of objects can be difficult when the stack >> > is not unwound. So with no stack unwinding the objects may have to >> > be designed to be able to do total clean-up without being destroyed, >> > which effectively means adding zombie states. >> >> No, you're going to stop the program anyway, which means the objects >> die automatically. > > There's no concept of object death in the standard. ;-) > > Assuming that you mean, those C++ objects are going to disappear: no more > process, no more objects. There's no concept of "process" in the standard ;-) Can we just speak English here? Yes, that's what I mean. > Yes, and that disappearance nearly without any trace, leaving behind > a mess Only a small fraction of objects on the stack in a typical program are responsible for any mess that might be left. > and little or no information about the detailed reasons, > is what we're trying to avoid. The detailed reasons generally have nothing to do with most of what was on the stack, and you're not going to instrument all of your classes to dump their state on destruction anyway. > C++ object destruction is more than internal-to-the-process object > disappearance. C++ object destruction is recursive, and often involves > external resources. To invoke that recursive destruction with cleaning up > of external resources and possibly logging of states, etc., Ah, but you're not going to log unconditionally, are you? You don't want to dump reams of log information for the normal control flow case! So you need global state, too, if only to decide whether to log something. > the objects have to be destroyed via the C++ mechanisms, not just > disappeared along with the process. No, they clearly don't. We can see that because many successful C++ programs exit without falling off the end of main(), some even in the normal case. Only _crucial_ cleanups need to be performed when aborting due to a broken invariant. > But ensuring C++ destruction of objects can be difficult when the > stack is not unwound. > > So with no stack unwinding the objects may have to be designed to be able to > do total clean-up without being destroyed, which effectively means adding > zombie states. I know what you mean, but in practice it doesn't matter especially because at that point we are on a very direct train to termination. > The TempFile object above is a bad example in this respect because a > file object typically has a possible zombie state anyway, at least > if it's designed to deal with errors. But replace "file" with > anything. "Balloon?" Sorry, I can't imagine a crucial resource for which a manager object should not have an "empty" state. Footnotes: [1] I'm being a little fuzzy about exactly where because I'd need to consult the standard to make sure I got everything right [2] We can argue later about whether those blocks are a good idea. -- Dave Abrahams Boost Consulting www.boost-consulting.com [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Gerhard Menzl on 15 Jul 2005 08:41 Peter Dimov wrote: > The two options aren't "abort and destroy hours of work" and "throw an > exception". The two options are "throw an exception" and "don't throw > an exception". > > In particular, nothing prevents the failed assertion handler to > attempt an emergency save, using a different file name (to not clobber > the "last known good" save), a different file format (if the native > format consists of a dump of the data structures and will likely > produce an unreadable file), and a different, extra-paranoid code > path. _Then_ abort. How can a global handler obtain the necessary context information to perform the emergency save? > Yes, I tried to gave an example of that in the other post. > Unfortunately, two important global invariants are "the heap is not > corrupted" and "there are no dangling pointers that are causing > damage", and a violation of those is usually manifested as a violation > of another (possibly local) invariant. I agree that in case of a corrupted heap (or stack), there is no point in continuing. But there are many local invariants that do not affect the global program state and thus hardly warrant an abort. How would you handle those? > A broken database connection, a corrupted table, a corrupted data file > are not logic errors. The number of logic errors in a program is > constant and does not depend on external factors. (Unless the program > itself changes, i.e. you consider the information in the database > "code", a part of the program.) Sort of. A considerable part of the program logic rests in stored procedures in the database itself. -- Gerhard Menzl #dogma int main () Humans may reply by replacing the thermal post part of my e-mail address with "kapsch" and the top level domain part with "net". [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Gerhard Menzl on 15 Jul 2005 10:48
David Abrahams wrote: > You seem to assume that aborting is the only alternative to unwinding > when a violated invariant is detected. In an interactive application > like a word processor it's usually possible to recover the state of > the document and offer the user an opportunity to save when a violated > invariant is detected. All of that can be done without any > unwinding. No, I was referring to an earlier posting of yours where you wrote: > If preconditions are broken, your program state is broken, by > definition. Trying to recover is generally ill-advised. and > Stop execution so I can debug the program. Good! My understanding is that recovery without unwinding requires a separate, handcrafted mechanism. Otherwise, how would you obtain the necessary context that is normally not accessible in a global, low-level function like an assertion handler? You argued (elsewhere) that the Undo feature in a word processor provides such a mechanism (or a substantial building block of one) anyway. Well, what about applications that don't have an Undo feature? > In that case, a corrupted database table is by definition *not* a > broken precondition. IIUC, you are expected to write software that is > guaranteed to work whether the database table is corrupt or not, and > you seem to accept that challenge. Great! It's similar to writing > software that is robust in the face of invalid user input. If the > user's input is invalid, well, there's some functionality they can't > get to until the input is corrected. Nobody I know would consider > valid user input a precondition in that case. I would not compare user input, which is always unpredictable, with the state of a database. If, as in my case, a database table is accessed exclusively by my application, which reads in data that have been written by itself during earlier sessions, corrupt data are closer to a broken invariant (or precondition) than to invalid user input. > In your application, calling database table integrity a precondition > is only going to confuse things and make your code more complicated. > Once you understand that database integrity is not a precondition, it > becomes very clear that you need to check for corruption in certain > places and make sure that you do something sensible if you detect it. I think it depends on whether you regard the database as an integral part of your system or something external. In a sense, it is both. An error caused by someone tinkering manually with the database, or by a broken network connection is external, but an error caused by your own application writing garbage that will lead to confusion during the next run is more like a logical error. -- Gerhard Menzl #dogma int main () Humans may reply by replacing the thermal post part of my e-mail address with "kapsch" and the top level domain part with "net". [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |