Personally I’m in favor of using implicit null-checks by default, as I’ll explain in the following. However I do sometimes use explicit checks, where the check is an important part of the algorithm. In other words, if the null-check is more than a regular sanity or validation check, I emphasize it with an explicit check. The exception is if I think that makes code less readable.
Reasons against explicit checks
Possible bugs
The issue with accidental assignment when comparison was the intend is only partially solved with the compiler warnings. Cases like this can still happen:
if ((possibly_null = nullptr) && (...)) {
...
}
This is a bug not caught by compilers. No warning in GCC for me.
It may happen in other common cases too (!).
Readability
Explicit null-checks introduce a negation, a level of indirection and with that a possible source of confusion. A regex search shows that it’s relatively common to do something like this:
const bool recalc_object = (ob->id.recalc & ID_RECALC_ALL) != 0;
const bool recalc_data = (object_data != NULL) ? ((object_data->recalc & ID_RECALC_ALL) != 0) : 0;
if (!recalc_object && !recalc_data) {
...
}
const bool is_multiview = (camera != NULL) && (scene->r.scemode & R_MULTIVIEW) != 0;
if (!is_multiview) {
return camera;
}
The explicit comparisons and double negations make this code rather confusing and a source of possible errors. Maybe some people can deal with this better than others.
Now, it’s fair to argue that there are multiple ways to make cases like this more readable. But my point is, it’s a common enough pattern to be a problem.
Alternative solutions
As Sybren noted, for him the purpose of explicit checks is the following question:
Can this pointer be NULL
at this point in the code?
A fair and important question.
In C++ there are other ways to answer it. The following is commonly recommended, the C++ Core Guidelines cover it too - roughly:
- “By default” you should pass objects by (const)
&
reference (if copies are not a reasonable option).
- When you have to rebind a reference, use a raw pointer but wrap it into a
non_null
template if applicable.
- Optional objects, like return values for lookups should use
std::optional<>
. If you need an optional reference, use a pointer - that’s what they are for. (std::optional<>
for reference types is not covered by the C++ standards and not supported by most compilers)
If applied consistently, most raw-pointer usages should vanish. A raw pointer not marked as non_null
should be null-checked before dereferencing.
Unfortunately C gives less options here. I think we should make good use of BLI_assert()
. Another thing we can use is ATTR_NONNULL()
, which helps a bit but has issues too (e.g. GCC only I think). Documentation can also help a lot.
Again, I’m not entirely against the explicit checks, I use them too sometimes. But I think these points should be considered.