Subject: Re: [boost] expected/result/etc
From: Gavin Lambert (gavinl_at_[hidden])
Date: 2016-02-11 18:32:00


On 12/02/2016 11:57, Emil Dotchevski wrote:
> It appears that you think that C++ exceptions are "unusual" in the same way
> OS exceptions are unusual: the OS detected something bad going on and
> raises an exception, as many OSes do for example in the case of
> dereferencing a null pointer. That's not at all what C++ exceptions are.
> They are in fact specifically designed to replace the need to return error
> codes, so that handling errors is simpler, safer and more testable.

Perhaps I'm biased by mostly developing on Windows+MSVC, but since that
implements C++ exceptions by raising OS exceptions, and OS exceptions
can be caught and processed just like C++ exceptions, I don't see any
distinction between the two. (Though I'm aware that the OS exception
handling mechanism is much more broken on non-Windows.)

And yes, I think that exceptions should be reserved for unexpected
cases. If a method has a case where it is expected to sometimes not
produce a value, then it should use optional<T> or similar.

> As for shared_ptr::operator*, I know it could be made to throw, but that
> would be incorrect design. I mentioned shared_ptr to make the point that
> you're wrong that this kind of design decision can not be made in generic
> C++ code. STL too is full of generic functions that throw to indicate a
> failure, and others that do not, without giving the user a choice. Even at
> the language level, consider that in C++ constructors don't give you the
> option to return an error code, the only way for them to fail is by
> throwing. Why? Because that is the correct design.

The only reason that shared_ptr::operator* does not throw is that the
class author decided that this is likely a hot path and the calling code
has *probably* already checked for null, so it is more *efficient* to
omit the check entirely (and cause undefined behavior if called in
violation of that assumption). As such an assert is used to *hopefully*
catch those violations, but there are only limited cases in which it
will do so. Checking and throwing will always be more *safe/correct*,
but for this particular method the author chose to prioritise efficiency
over safety.

(Although shared_ptr has an additional bias -- if the operator*
precondition is violated then even if the assert is omitted it's almost
certainly going to immediately cause an OS fault anyway due to a null
(or nearly-null) pointer access -- which is arguably equivalent to
always-throw behaviour. Methods on other classes typically won't get
that for free.)

Don't get me wrong, I'm not saying that this is a bad thing. But it can
also get applications into trouble and it is useful to provide both
options. Just look at all the discussion recently about safe numeric
libraries -- that's another case where the language has chosen to value
efficiency over safety, and some applications wish for the reverse.