On Sun, Oct 6, 2013 at 6:14 PM, Gavin Lambert
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