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.