Relating to yesterday's post, it also bothers me that I have to include a header just to get NULL. Actually, it doesn't bother me at all, because I never use it. Literal 0 does everything I need and more: it doesn't require a #include and is 3 fewer characters to type.
NULL is simply #defined to 0, at least in C++. It cannot be defined to (void*)0 as it sometimes is in C, because that would break C++'s stricter conversion rules:
int* p = NULL; // error - cannot implicitly convert void* to int*
I have seen claims that using NULL rather than 0 is a useful piece of documentation that shows readers of the code that a function argument is pointer-like rather than numerical, but I've always found it to be misleading at best. I've seen plenty of places (typically in Win32 code) where NULL is used where a numeric value is needed, and of course the compiler accepts it without question.
It can even be dangerous as calling an overload may not give you the result you expected:
void f(int); // 1 void f(void*); // 2 f(NULL); // Calls 1
Similarly when instantiating a template (which has a direct impact on perfect forwarding):
template <typename T> void f(T); f(NULL); // instantiates f<int>
Some implementations (e.g. GCC) will define NULL to a compiler-specific construct which will warn when using it in a non-pointer context, but it's not something you can rely on if you're working on cross-platform code. Not only that, but it will still have the same semantics as a literal 0, so you'll still get the unexpected overload and template instantiation semantics mentioned above.
C++11 has the nullptr keyword, which theoretically should solve all of these problems. NULL can't be redefined as nullptr, because that would break programs which are using NULL incorrectly, so existing code must be changed to use it explictly and new code must adopt it. This puts a further nail in the coffin of NULL.
As for nullptr, all is fine unless you're writing code which is designed to be target and compiler-independent. Why? Well, nullptr is also the name for C++/CLI's null reference literal, and it's not compatible with C++'s version. When it came to resolving the ambiguity between them when compiling conforming C++ code with the /clr switch on, Microsoft made the decision to side with their own technology rather than the C++ international standard:
http://channel9.msdn.com/Shows/Going+Deep/Stephan-T-Lavavej-Everything-you-ever-wanted-to-know-about-nullptrThey provide __nullptr instead, which will always be a 'native' nullptr, but of course this is a Visual C++ extension. It won't work when you are writing code which doesn't want to care about which compiler is building it. If that is your goal, you'll need to encapsulate it in a macro which expands out to __nullptr or nullptr depending on your compiler. Which is worse than the NULL macro!
So it's best to simply get used to the fact that literal 0 can mean a null pointer. It still fails in the context of perfect forwarding, but that can be fixed by an explicit cast. Otherwise, literal 0 is portable, doesn't require pulling in an unnecessary header and doesn't result in surprising behaviour with respect to overloads or template instantiations.
It's going to be with us for a while yet.
No comments:
Post a Comment