|
Boost : |
From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-10-26 17:00:37
----- Original Message -----
From: Darin Adler <darin_at_[hidden]>
To: Boost <boost_at_[hidden]>
Sent: Friday, October 26, 2001 3:16 PM
Subject: [boost] casts, exceptions, and asserts
> In previous threads on boost, we've clarified the distinction between the
> roles for exceptions and asserts. Let me summarize:
>
> A) Some functions have defined behavior when out-of-domain arguments
are
> passed in. Typically this behavior is throwing an exception. One case
where
> this is required in cases where the caller can't be expected to know
whether
> an argument is in or out of the domain.
> An example of this would be a function that converts a string to an
> integer. The caller can't be expected to check whether the string has the
> proper format, because that's nearly equivalent to converting the string
to
> the integer in the first place.
>
> B) Other functions have undefined behavior when out-of-domain
arguments
> are passed in. This allows for more-efficient implementation, since the
> function can assume that the argument is good. For debugging purposes,
such
> functions can still check the validity of the arguments. This is typically
> done with an assert, but some programmers prefer to use exceptions. But
even
> if an exception is thrown, this is not part of the defined behavior of the
> function. People often emphasize that the behavior of such a function when
> passed a bad argument is a quality of implementation issue.
> An example of this is the [] operator for class vector.
Implementations
> are free to do range checking on the argument passed in, but the behavior
is
> not defined. Many in the boost community have suggested that the
> implementation this function should include an assert.
>
> A minor problem in boost is that we don't make a clear distinction between
> which functions are functions of type A and which are functions of type B.
A
> pedantic distinction can be made as well, because the choice of strategy A
> vs. strategy B can be different for different parameters in the same
> function, or even for different kinds of "bad" arguments.
>
> ================
>
> One reason this is a bit confusing is that the C++ standard, by
definition,
> doesn't address the "assert vs. exception" choice for functions of type B.
> This could lead programmers to think that exceptions are always better
than
> asserts, since the standard mentions exceptions (when defining functions
of
> type A), but does not include any example of asserts as part of defined
> behavior.
>
> Another issue is that some programmers want to turn all asserts into
> exceptions. While this may sound like a good idea, it has many of the same
> drawbacks of the Windows practice of turning machine exceptions into C++
> exceptions. When it comes to undefined behavior, we need to make programs
> that don't rely on it at all. Asserts are tools that we can use to correct
> our programs, but if they are turned into exceptions we end up writing
code
> to "handle" the undefined state. This issue leads some programmers to
> suggest eschewing asserts altogether and abandon the "debug vs. release"
> build model where the asserts are removed from production programs.
>
> ================
>
> In light of this, lets consider the cast.hpp header for a moment.
>
> polymorphic_cast trades one kind of defined behavior (returning 0) for
> another kind of defined behavior (throwing an exception). But it seems to
me
> that the whole point of the function is that it's used when you know the
> case will succeed. That seems to put in in category B from above. I'd like
> to see this function use an assert rather than an exception.
>
> polymorphic_downcast does an assert for the same reason I'd suggest
> using an assert in polymorphic_cast. It's strange to have one member of
the
> family using an assert and another using an exception.
>
> numeric_cast is a function of category A and always does range
checking
> and throws bad_numeric_cast if the result of conversion is out of range.
But
> I'd like to see a numeric_cast of category B, that leaves out the range
> checking in NDEBUG builds, for use when the number is known to be in range
> and a programmer wants an assert to check this assumption.
>
> -- Darin
>
I agree with the general form of the concepts that you have presented here.
However, I think that the root of the problem is not *exactly* the 'assert
vs exceptions' issue. Let me explain:
An 'assertion' is an "operation". It has a well defined meaning, that of
aborting program execution when a condition is detected which, if the
execution would continue, would result in undefined behaviour.
An 'exception' is a "mechanism". It dosen't has a meaning in it self.
'throwing this particular exception upon failure", OTOH, is another
operation.
Let's call it: 'error detection'.
I agree that a given function might include 'error detection'. These are
type A functions.
I also agree that 'error detection' is a well defined behaviour, and thus is
part of the function interface.
I agree that the function user can expect the function to detect errors and
signal them out, so he is enabled to "handle" those errors.
I also agree that a given function might additionally be able to trap
conditions leading to undefined behaviour and deal with it. These are type B
functions.
This isn't part of the function established behaviour, so the function user
shouldn't handle this -because he is not supposed to expect this as a
possible outcome-.
But 'exceptions' themselves have nothing to do with all the above.
I can say: this function 'throws an exception in case of error', so this is
an error detection, and the user knows about that;
Or, I can say nothing, and silently throw an exception in the case of an
assertion failure.
But this exception isn't part of the interface, it isn't even visible to the
user, so I don't risk the user trying to handle it.
In other words; I think that the correct distinction is not between asserts
and exceptions, but between asserts and error detection.
The later uses the exception mechanism explicitely.
The former is implementation specific. It can be implemented in terms of a
thrownn exception invisible to the user. (I'll explain below why this is a
good idea)
Just as an example, in my own code I use a defect managment framework
that -among other things- provides:
ASSERT(cond) // with different flavors
ERROR_DETECT(cond,except) // with different flavors.
ASSERT "conceptually" aborts program execution by means of throwing
'assertion_failure_exception'.
The exception isn't part of the function interface and the user code doesn't
handle it (*see below).
ERROR_DETECT throws the specified exception in the case of an error handled
by the function.
This behaviour is part of the interface, 'except' is visible to the user and
he is expected to handle the exception.
Now, it remains to see why it is a good idea to use a 'almost-invisible'
exception to implement assertions: because it gives the chance to the
highest level control loop to leave the program in a controlled way or even
continue execution when this is possible.
This might not be reasonable for a console application but it is fundamental
in most event-driven applications.
A typical event-driven application won't handle the
assertion_failure_exception directly anyway. It just typically uses a 'catch
all' (including OS exceptions) in the main loop and with a design in which
events are decopled, so "catch all and continue" is usually safe.
I said "almost invisible" because in the debug version, the "catch all" in
the main loop actually does handle the exception explicitely only to log out
the problem.
In summary, I agree that we need to identify when we want to throw an
exception that the user is aware of, and is supposed to be able to
handle -something that I call 'error detection"- and differentiate it from
the assertion test, which uses the exception mechanism to
"almost" abort, throwing an exception "assertion_failure_exception" which is
supposed to be handled only at the level when every exception must be
handled because the application must keep running.
Regarding numeric_cast_traits<>, I agree that we need the 'assert in debug
mode' only versions. Anyway, notice that the optimized code that I presented
actually bypasses the range checking when it determines at compile time that
it isn't necessary, for instance, when converting from 'float' to 'double'
Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk