Boost logo

Boost :

Subject: Re: [boost] Looking for thoughts on a new smart pointer: shared_ptr_nonnull
From: Gavin Lambert (gavinl_at_[hidden])
Date: 2013-10-07 00:34:36


On 10/7/2013 4:37 PM, Quoth Matt Calabrese:
> 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.

I have yet to see anything that convinces me that there is a valid
reason for that. Maybe I missed it, or maybe we just don't agree on
this aspect of code design. :)

> Here's an actual solution. Don't violate your function's precondition and
> therefore don't have UB.

That's nice to say in laboratory conditions. Try doing it in any
real-world application without *something* validating your preconditions
and see how far it gets you.

Failing early and fast on programmer errors *when the programmer is
around* is the most important thing. Being able to keep the system
running (without UB) in actual usage by detecting and recovering from
unexpected failure is the second most important thing. (Reverse the
priorities of these if you are the customer.)

(Note that I'm not talking about "catch exceptions everywhere and maybe
log them if you're lucky" type 'recovery', but *real* recovery at
appropriate points that know how to repair state and resume normal
execution.)

> 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 thatis important to
> recognize and understand.

I just don't understand why you don't seem to think that the constructor
of the "guaranteed not null pointer" is a reasonable (and essential)
place to verify that condition, for the sake of code sanity.

Pushing the responsibility higher up the call chain just increases the
chances that someone is going to forget, and *introduce* UB. That's not
the direction you should be going.

> 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?

No, I don't. You don't run tests on your release builds too?

And again, any real-world system that actually wants to recover from
such errors will already be handling exceptions. Any that doesn't will
just std::terminate anyway when it hits the top of the thread, so you
don't lose anything.

And nothing is relying on UB. The exception is thrown *before* UB occurs.

> 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.

Yes, it is a programmer error. People forget things. It happens. When
it's an easy mistake to make, and easily avoided (via exception), why
not defend against it rather than simply allowing the code to enter a UB
path? UB is bad. Don't encourage it.

And it's not using exceptions as control flow, because it's not a normal
execution path. Programmer errors are by definition an abnormal
execution path.


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