First of all, note that this is a special case of checking contracts: you write code that does nothing but check at runtime that a documented contract is being executed. Failure means that some code is somewhere defective.
I always doubt a little the implementation of special cases of a more useful concept. Checking contracts is useful because it catches programming errors when it first crosses the API boundary. What is so special about zeros that means they are the only part of the contract you want to check? However,
Regarding input validation:
null is special in Java: many Java APIs are written so that null is the only invalid value that can even be passed to a given method call. In such cases, a null check "fully validates" the input, so the full argument is used in favor of checking the contract.
In C ++, on the other hand, NULL is just one of almost 2 ^ 32 (2 ^ 64 on new architectures) invalid values that a pointer parameter can have, since almost all addresses are not objects of the correct type. You cannot "fully confirm" your entry if you do not have a list of all objects of this type.
The question then becomes, is NULL a common enough invalid input to receive special treatment that (foo *)(-1) does not receive?
Unlike Java, fields do not get automatically initialized to NULL, so an uninitialized garbage value is as believable as NULL. But sometimes C ++ objects have pointer elements that explicitly have a NULL value, which means "I haven't got it yet." If your caller does this, then there is a significant class of programming errors that can be diagnosed with a NULL check. An exception may be easier to debug than a page error in a library for which they have no source. Therefore, if you do not mind bloating the code, this can be useful. But this is your interlocutor that you should think about, and not about yourself - this is not protective encoding, because it only "protects" from NULL, and not from (foo *) (- 1).
If NULL is not a valid input, you might consider using a parameter by reference rather than a pointer, but many coding styles do not approve non-constant reference parameters. And if the caller sends you * fooptr, where fooptr is NULL, then it was of no use to anyone. What you are trying to do is compress a little extra documentation into a function signature, in the hope that your caller is more likely to think: "Hmm, maybe there is no zero here?" when they should explicitly dereference it, than if they just passed it to you as a pointer. It still comes, but as far as possible it can help.
I do not know C #, but I understand that it, like Java, is guaranteed to have valid values in these links (at least in safe code), but unlike Java, in which not all types are NULL. Therefore, I assume that null checks there are rarely worth it: if you are in safe code, do not use a type with a null value, if null is not valid, and if you are in unsafe code, then the same considerations apply as in C + +.
Regarding verification of withdrawal:
A similar problem arises: in Java, you can "fully test" the result by knowing its type and that the value is non-zero. In C ++, you cannot "fully check" the result with a NULL check - all you know is that the function returned a pointer to an object in its own stack that has just been unwound. But if NULL is a common invalid return due to constructs commonly used by the caller’s author code, then checking it will help.
In all cases:
Use assertions rather than “real code” to check contracts wherever possible - as soon as your application is running, you probably do not want each caller's code scatter to check all his inputs, and each caller to check his return values .
In the case of writing code that is portable for non-standard C ++ implementations, instead of the code in the question that checks for null and also catches an exception, I would probably have this function:
template<typename T> static inline void nullcheck(T *ptr) { #if PLATFORM_TRAITS_NEW_RETURNS_NULL if (ptr == NULL) throw std::bad_alloc(); #endif }
Then, as one of the list of things that you do when porting to the new system, you correctly define PLATFORM_TRAITS_NEW_RETURNS_NULL (and possibly some other PLATFORM_TRAITS). Obviously, you can write a header that does this for all the compilers you know about. If someone takes your code and compiles it on a non-standard C ++ implementation that you know nothing about, they are fundamentally independent for more reasons than this, so they will have to do it themselves.