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.) Sure, for performance reasons you want to elide the check that the precondition has been met by the supplied arguments. I get that. 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. 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). The key point is that it is far better to detect that UB is about to happen and to throw an exception than it is to allow that UB to happen. It is absolutely NEVER correct for library code to call std::terminate on any error (with the single exception of something that represents a top-level caller that would do that in the OS anyway, such as a thread).
My point isn't that preconditions, in a general sense, are easier or harder to work with than exceptions. I don't think that that is something anyone can say in a general sense. The point is that preconditions and exceptions serve two distinctly different purposes, so we are comparing apples to oranges. In this case, only an apple makes sense, so it's already end of story right there.
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] - operator-> and other access methods would assert() that the internal pointer is non-null but *not* otherwise check. 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. If the internals encounter null pointers, however, this is a definite indication that UB has already occurred at some point (probably a buffer overrun somewhere), because the constructor should have eliminated all other cases of such. There is no point in defending against this in release code as it's just as likely that a garbage value was written instead of a null, but having an assert may help track it down when debugging (though it's probably about to segfault anyway). Paranoia at interface boundaries is the fastest way to track down bugs, and should only be removed if there is some demonstrably significant performance benefit in doing so.