Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!usc!sdd.hp.com!cs.utexas.edu!uunet!xavax!jat From: jat@xavax.com (John Tamplin) Newsgroups: comp.lang.c++ Subject: Re: Destructors & Exceptions (long) Message-ID: <1991Jun30.220555.6654@xavax.com> Date: 30 Jun 91 22:05:55 GMT Organization: Xavax Lines: 99 In article <1561@appli.se> niklas@appli.se (Niklas Hallqvist) writes: ... discussion of problems with destructor during exception handling ... >class Transaction { > // ... >public: > void begin_work(); > void commit_work(); > void rollback_work(); >}; > >int bar(); // returns 1 if OK, 0 otherwise > >void foo() { > Transaction t; > t.begin_work(); > if(bar()) > t.commit_work(); > else > t.rollback_work(); >} > >Now, if the bar() provider rewrites bar() to throw an exception if >bar() is not successful, and returns ordinarily otherwise: > >void bar(); // Might throw WRITE_FAILED, NO_MEMORY > >How do we rewrite our code? First of all we note what's been said >in the ARM 15.3c and add another class: > >class MyTransaction { > Transaction& t; >public: > MyTransaction() { t.begin_work(); } > ~MyTransaction() > { if(/* in exception context */) t.rollback_work() else t.commit_work(); } >}; > >// Hmm, wait a minute, how do I do that? Well, I'll let it be for now... > >void foo() >{ > MyTransaction t; > bar(); >} > >As you can see, we've got a problem here, and the cause is of course that >we've moved the real work into the destructor to be safe from the potential >exceptions bar() might throw. The usual solution is to write a try block >everywhere transactions are used, and it will of course work, but it'll >be a lot of redundant code since if the exceptional condition could be >checked-for in the destructor, only one check will be made, instead of >many scattered out through the application. This possiblity would serve >as a nice encapsulation of exceptional handling. How could we achieve this? >My ideas are: I have done almost the exact thing without trouble. My example: class MyTransaction { Transaction &trans; public: MyTransaction(Transaction &t) trans(t) { trans.begin_work(); } ~MyTransaction() { if(trans.in_progress()) trans.rollback_work(); } void commit() { trans.commit_work(); } void abort() { trans.rollback_work(); } }; void foo() { Transaction t; ... non-transaction stuff ... { MyTransaction mt(t); bar(); mt.commit(); } } If your implementation of Transaction can't tell you if it is in progress, you can add a state flag to MyTransaction and eliminate the need. I think that this methodology also solves the general case where you want the destructor to do different things based upon previous actions -- you just tell the object before it gets destructed. >Well... What do you net.people say? Will this be such an unusual situation >so you won't worry, or do you feel like me, now when exception handling has >come to stay, the destructor of an object should be able to find >out the context it got called from? Another interesting side-effect of >allowing this is that it should be plausible to throw exceptions from a >destructor without having to worry about if an exception has already been >thrown. I think the C++ compiler should already be able to handle that, at least according to my interpretation. -- John Tamplin Xavax jat@xavax.COM 2104 West Ferry Way ...!uunet!xavax!jat Huntsville, AL 35801