On Sun, Oct 6, 2013 at 9:34 PM, Gavin Lambert
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. :)
I think you either missed or didn't get it. 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.
Someone /is/, or should be, validating your preconditions (it's you). That is precisely what a precondition is. 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.)
Again, if you can keep the system running in the case where you would be passing the null pointer, then it is your responsibility to do that.
(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.)
If it's possible to do real recovery in cases where you /would/ pass a null pointer, then it is your responsibility to be either immediately handling the situation or throwing your own exception. 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.
Your sanity check is the assert. 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.
We're not pushing the responsibility to meet preconditions /up/ at all. It's exactly where it should be (some point prior to calling the function). We're talking about avoiding pushing the responsibility /down/ into a function where the precondition was already violated (where we would already be in UB land).
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?
When did I say that I don't run tests on release builds? The point is you are knowingly introducing a completely separate code path that only occurs in release builds, that is only encountered as a part of UB, and that you are claiming is perfectly valid and to be relied upon. If you somehow have the ability to handle this case, why would you not want to be handling it and testing it in a debug build? As well, if it's a valid code path, then you shouldn't be relying on UB to get you there. In other words, just validate your precondition and handle it if it's not met.
And nothing is relying on UB. The exception is thrown *before* UB occurs.
No, it's not thrown before UB occurs. By having a function with preconditions and calling that function with values that don't meet those preconditions, you have undefined behavior. If you want specified behavior, then it's not a precondition. That said, I've already also explained why it should be a precondition. Please, let's not turn this into a vector at().
Yes, it is a programmer error. People forget things. It happens. When it's an easy mistake to make, and easily avoided (via exception)
An exception does not "avoid" anything. The mistake is still made and it is still a bug. , why not defend against it rather than simply allowing the code to enter a
UB path? UB is bad. Don't encourage it.
No one is encouraging UB. And it's not using exceptions as control flow, because it's not a normal
execution path.
I challenge you to define what you even mean by this. Do you mean that the code path is simply not common, and that this somehow makes things acceptable? There are plenty of branches in code that are not "normal." -- -Matt Calabrese