Boost logo

Boost :

Subject: Re: [boost] Looking for thoughts on a new smart pointer: shared_ptr_nonnull
From: Matt Calabrese (rivorus_at_[hidden])
Date: 2013-10-06 23:37:45


On Sun, Oct 6, 2013 at 6:14 PM, Gavin Lambert <gavinl_at_[hidden]> wrote:

> On 10/7/2013 7:14 AM, Quoth Matt Calabrese:
>
> You can't deal with an exception here because it was caused by a
>> programmer
>> passing in a value that violated the preconditions of the function. In
>> other words, the programmer thought that he was passing something that met
>> the preconditions, but he was incorrect. This is a bug and his handler
>> can't fix the bug. He thought he was passing in one thing but was really
>> passing in something else, so you assert. The reason that you can deal
>> with
>> exceptions elsewhere is because they are thrown due to conditions that the
>> caller simply can't account for at the time the arguments are passed, for
>> instance, because those conditions may be nondeterministic from the
>> caller's point of view. Such exceptions do not get thrown because of
>> programmer error.
>>
>
> Why not? (This is the point of std::logic_error after all.)
>

std::logic_error is an example of the fallibility of the standards
committee and simply should not exist (and similarly, vector's at()). There
are plenty of people involved in standardization, some of whom are boost
developers, that have explicitly acknowledged this (one is even known for
formally defining the exception guarantees, and if you say his name three
times, he'll probably appear in this thread). Not that authority at all
matters, but be aware that we are not insane and alone here. We've
expressed precisely why throwing an exception in this case isn't a good
idea and it's both understood and accepted by many. If you don't
immediately get the rationale or if it seems unintuitive, please try to
really follow the reasoning and be open to changing your opinion.

Sure, for performance reasons you want to elide the check that the
> precondition has been met by the supplied arguments. I get that.
>

I haven't mentioned performance because that is entirely besides the point.

> But that is a different argument from saying that it is somehow "wrong" to
> include that check and throw if they are not met, which is what it sounds
> like you are arguing.
>

If you are violating your precondition, throwing is not a proper solution
for the reasons already explained several times at this point. Why the
non-nullness of the pointer should be a precondition was also already
explained.

> And sure, the code that calls that constructor might not be prepared to
> deal with exceptions. But that's the entire point of exceptions -- to walk
> all the way back up the call stack until it finds the code that *is*
> prepared to deal with the exception (perhaps by cancelling a particular
> task, perhaps by tearing down and recreating an object or entire module or
> subsystem, perhaps by aborting the entire program -- but this decision must
> be in the hands of the application itself).
>

Here's an actual solution. Don't violate your function's precondition and
therefore don't have UB. If your input can potentially violate the
preconditions of the function, you check them before passing them off. If
there is some way to handle your null case, you need to be doing so at some
point before you pass that null pointer. Keep in mind that it's still often
entirely acceptable for the would-be caller of the non-null shared_ptr
constructor to throw an exception on null (I.E. if /that/ function's
preconditions were met, but the null pointer implies that the function
cannot meet its post conditions). There is a very big distinction here that
is important to recognize and understand.

What I would consider reasonable behaviour for this sort of "guaranteed
> non-null pointer" is the following:
>
> - constructor will assert() and then throw an exception on receiving a
> null argument [the first because it's a programmer error, the second
> because asserts might be disabled]
>

So in other words, you are writing a hypothetical "handler" that you've
never even seen triggered/tested in a debug build (because it can't even
get there in a debug build) and this handler exists because you might have
some way to handle the null case and it is simply inexplicably a better
choice than not violating a function's precondition and relying on UB? You
don't see any flaws in this logic?

This is because it is reasonable for the constructor of the class to
> receive null pointers by accident via simple programming errors (and hands
> up everyone who has never made any of those). These are also potentially
> recoverable errors because it does not necessarily signify that anything
> has gone completely wrong.
>

What you are describing is a programmer error (bug). If it is /not/ a
programmer error and the pointer being null is simply a possible case
before calling the constructor, you should be doing that check before
passing it off rather than using an exception as control flow. Again, your
way of handling it may end up being to throw an exception, and in some
cases that is a proper thing to do, it's just /not/ appropriate for the
constructor.

-- 
-Matt Calabrese

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk