On 10/7/2013 11:47 PM, Quoth Julian Gonggrijp:
While I'm not Matt Calabrese, I think I can restate why it should be a precondition *especially* on the constructor. The entire point of the proposed non-null pointers is to guarantee that the pointer is not null, so that receiving functions and storing objects can assume that it is not null. If you zero-initialize a pointer whose very purpose is not to be null, you defeat the purpose of its existence.
This sounds to me like an argument *for* the position I am suggesting, not against it. Throwing an exception means that the internal bare pointer is never initialised to null (or at least that if it is initialised to null, it can never be accessed as such because the resulting object is not retained), which means that it will never be accessed in an undefined way. If you fail to throw the exception (and to assert, because you're in a release build) then you *do* go on to set the bare pointer to null, and *the program does not immediately fail*. By contrast, it will fail at some unknown later point at which someone actually attempts to access the pointer. If you're lucky, that's in the following line, and the bug is easier to find. If you're unlucky, it's three hours later (because the pointer was created as a member field in another object that is only accessed rarely) and it's much harder to figure out what happened. Checking in the constructor is instant and failsafe. Not checking is just leaving a time bomb in the code.
Note how this is entirely analogous to std::sort, whose purpose is to perform an operation on a valid range. If you pass an invalid range, you defeat the purpose of the existence of the algorithm. This is why such algorithms have the precondition that you pass a valid range, IOW, that you only use them for what they were meant to be used for.
The difference in that case is that there is no practical way to test for an invalid range, so you have no choice but to rely on the caller getting it right. (At least not without help from the iterators themselves, which is sometimes supported but implementation-specific and thus not reliable.) Verifying that a supposedly-not-null bare pointer is in fact not null is a trivial check, and one that it seems worthwhile to do prior to storing that pointer in an object that wants to guarantee that its stored pointer is not null.
The simplest way to do that is by initializing all non-null pointers with make_shared or another non-null pointer.
make_shared can fail and throw an exception. So the user already has to be prepared to deal with that concept anyway. And while I don't think boost's implementation supports it, it's not unreasonable to think that some STL's implementation of std::make_shared could return an empty pointer if the user has configured "new" to return null instead of throwing an exception on failure, which would then push the responsibility of throwing the exception onto the not_null_ptr's constructor, or of needlessly complicating every single construction site to avoid UB.