On 23.02.2016 23:19, Mr Flibble wrote:
> Code containing asserts will behave differently depending on whether or
> not the macro NDEBUG is defined. This is dangerous as it may result in
> undetected bugs in a release build manifesting as undefined behaviour
> that doesn't cause a straight crash but does something more serious.
Well, you can define a wrapper that throws an exception in NDEBUG
(a.k.a. “release”) mode, then preferably a “hard exception” that has
maximum chances of propagating all the way up to `main`.
The main problem with that, as I see it, is that cleanup via stack
unwinding can be dangerous when an assertion has failed.
But I think the chances favor attempted cleanup as typically less
destructive, less costly to the user, than a simple crash or just
continuing with UB, which can do all kinds of Very Bad Things™. Of
course one might ask the user. That's what the robots in Asimov's novels
failed to consider, to just ask those that they served, before going all
out with xenocide of the galaxy and mind control of humans.
Anyway, as bonus points such a wrapper can do away with the silly
restriction that the `assert` argument must be of scalar type (in C++ it
can be enough that it's convertible to `bool`); the wrapper can possibly
include logging; and the wrapper can be `constexpr`, which unfortunately
is not guaranteed for plain `assert`.
See <url:
https://github.com/alf-p-steinbach/cppx/blob/master/source_code/cppx/basics/execution/assertions/CPPX_ASSERT.hpp>
for an example (does not include logging, nor asking, but is `constexpr`).
> One should prefer exceptions to asserts. One should have many exception
> types and many throws (a throw statement should have no performance
> impact in a decent implementation for the code path where no exception
> is thrown) but few try/catches.
I think this is much like an argument that one should prefer runtime
type checking, namely based on a conflation of issues, and designed to
make one particular chosen approach to things seem preferable.
An assertion expresses an INCORRECT ASSUMPTION, strongly associated with
preconditions.
This is a contract breach by a caller, or by preceding code, and it
indicates something wrong up there, often with no way to be sure exactly
where or what.
Throwing an ordinary exception misinforms the calling code, which will
handle the ordinary exception as if this code, not itself, had failed.
An exception (a standard exception) throwing, OTOH., expresses a GOAL
ACHIEVEMENT FAILURE, strongly associated with postconditions.
This is a local failure. One knows what's wrong, e.g. some resource
exhausted, some item not found, whatever, including the case where some
lower level function failed, possibly via exception. And the design
called for such failure to be communicated back up via an exception.
> I recommend having a top level try/catch in main() and a few other
> try/catches where appropriate (for exceptional cases that are
> recoverable without terminating the application).
Yes, I agree that's useful.
In particular, otherwise a Windows GUI subsystem program can just
silently terminate...
And there's also the issue of stack unwinding not being guaranteed for
an unhandled exception.
> Remember: asserts considered harmful.
No. :)
Cheers!,
- Alf