
On Tue, Jul 19, 2011 at 5:41 PM, Dave Abrahams <dave@boostpro.com> wrote:
on Tue Jul 19 2011, lcaminiti <lorcaminiti-AT-gmail.com> wrote:
Dave Abrahams wrote:
on Tue Jul 19 2011, Lorenzo Caminiti <lorcaminiti-AT-gmail.com> wrote:
I'm sorry I managed to confuse everyone with this post because I meant to ask something different...
My question instead, is: What shall I do if I fail to copy an oldof value?
I don't think I misunderstood your question, although I don't happen to know what "oldof" means here...
Specifically, what shall I do if an oldof copy throws an exception? This is not specified by N1962 (as far as I can see). I decided that if I fail to copy and olfof value then I call the postcondition_broken handler but only after executing the body (even if oldof values are internally copied before the body is executed).
As I said, that doesn't make any sense to me, for the reasons I already gave, and because failure to allocate memory in precondition checking does not amount to a broken postcondition.
But in this case the failure is in allocating memory to copy an old value and NOT in checking preconditions.
Failing to copy an old value (e.g., for an alloc failure) will indeed not allow to check postconditions because the postconditions use the old value. Therefore, the postconditions should be considered failed because they cannot be checked and postcondition_broken should be called.
No (IMO). postcondition_broken indicates a program bug. As I mentioned in my guidelines, anticipated failure to satisfy a postcondition is a recoverable condition and should normally result in an exception.
Of course, if that would change the contract of the function, it's unacceptable ;-)
I see your point... what to do if contract checking (including old-of copies) throws an exception is a debatable point (also mentioned in a previous rev of N1962). However, if you decide to throw the contract exception outside the called function there are a couple of important issues to keep in mind: 1) The caller might not except such an exception because is not among the ones the body throws. You already mentioned the extreme case of throwing an alloc error because of an old-of copy failure for a body that does not throw (throw() exception spec)-- the caller will handle no exception because of the throw() spec of the body. 2) The exception might be thrown when checking class invariants at destructor entry so the destructor execution will throw violating STL exception safety rules. To address 2), N1962 finally decided to: `` It is always called as if surrounded by a try-catch block: try { class_invariant(); } catch( ... ) { std::class_invariant_broken(); } rationale: this ensures that exceptions thrown in the invariant cannot escape from functions and in particular not from the destructor. [6] `` In other words, an exception thrown by class invariant checking will call the class invariant broken handler. I think the best and most consistent why to handle the more general issue of exceptions thrown while checking a contract is to always call the broken handlers (so not just for class invariants but also for preconditions, postcondition, static class invariants, block invariants, and loop variants). Note that this neither terminates nor throws, it simply calls the handlers that only by _default_ terminate. Note that in Boost.Contract you can redefine the broken handlers to re-throw exceptions not thrown by a contract condition failure (e.g., a runtime_error) but to still terminate otherwise therefore achieving the behavior you described (you will need to pay special attention to destructors). For example, set the broken handlers to: void david_handler(from const& context) { try { throw; // re-throw to handle active exception } catch(contract::broken& condition_failure) { // a contract condition was evaluate false, it's a bug terminate std::terminate(); } catch(...) { // some other hopefully recoverable error (alloc, etc), rethrow unless from destructor entry if(context == FROM_DESTRUCTOR) std::terminate(); else throw; // exception will fly out to the caller } } set_precondition_broken(&david_handler); set_postcondition_broken(&david_handler); set_class_invariant_broken(&david_handler); set_block_invariant_broken(&david_handler); set_loop_variant_broken(&david_handler); Here, 2) is explicitly addressed by terminating for destructors all the times. While 1) should no longer be a concern because if programmers redefine the handlers to throw it makes sense that they also program the callers to handle exceptions thrown by the contracts even if the body is known to not throw them (based on its exception specifications, documentation, or similar). In summary, while I have seen this issue debated both ways, I think the concept "the contract broken handler is called unless the contract condition is evaluated to be true" is clear and simple (this includes the case of a contract condition evaluated to be false, an unercoverable? bug, but also a contract condition that cannot be evaluated because it throws, a recoverable? error). At the end, programmers can configure the broken handlers to take the actions they need (in fact, in some applications even a bug might be better handled by starting a "return home" procedure instead of terminating the program). I will add this example to the docs. Thanks, --Lorenzo