Re: Macro NULL or 0
On May 25, 3:17 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
Ioannis Vranos wrote:
Hi, in a discussion I am having with a programmer, we have a
dispute if it is a good idea to use the macro NULL instead
of 0 for denoting a null pointer value.
This post is guaranteed to start a holy war.
That's highly likely:-). (For the record, most programmers I
know, including myself, prefer NULL. But there are reasonable
arguments for 0 as well---in particular, people doing a lot of
template programming or abusing overloading tend to prefer 0.)
For the record, I don't care for 0 or NULL, nor do I see any
need for the new nullptr keyword. I prefer to use a
default-constructed rvalue of the specific type I want.
I went that way for a short period of time. Not a
default-constructed rvalue---that didn't exist back then. But a
macro which took the type as an argument, and returned a
correctly converted 0. In theory, it should be better---more
type safety and all that. In practice, one quickly tires of
writing "null( MyOuterNamespace::MyInnerNamespace::MyClass* )",
when simply NULL will do the trick. Null (or nullptr) seems to
be a compromise which gives enough of the type checking
advantages, without being too verbose. (This seems to be the
experience of other languages, with stricter type checking, as
well.)
Aside from their other problems, "every-null" values cannot
possibly work properly with function overload resolution,
since they do not encode enough static type information.
For example:
template<typename T>
bool is_pointer(T) {
return false;
}
template<typename T>
bool is_pointer(T*) {
return true;
}
struct value_t { };
/* Introduce a compile-time abstraction, rather than hard-coding use
* of raw pointers in subsequent code. I have found this to be
* worth doing.
*/
typedef value_t* value_pointer;
Most people I know (myself included) consider this more
obfuscating than otherwise. (But I don't think we've ever tried
it where pointer was spelled out in clear, rather than using
some obfuscating abbreviation.)
In a few cases, I have found it useful---in such cases, I'll
usually use a typedef in the class, so the name of the pointer
type is Value::Ptr. (The Ptr is historically conditioned; were
I inventing the idiom today, I think I'd write Pointer.) Most
of the time, however, this is because the class is designed to
be used with smart pointers, and the typedef is a smart pointer.
#include <iostream>
int main() {
/* False. Integer literals smell bad. */
std::cout << is_pointer(0) << '\n';
/* False. Macros smell bad. */
std::cout << is_pointer(NULL) << '\n';
/* True. Type-safe. Flexible. */
std::cout << is_pointer(value_pointer( )) << '\n';
And what does
std::cout << std::nullptr << '\n' ;
give?
To tell the truth, I'm not sure, but I have a sneaky feeling
that it is false. The standard overloads template functions for
nullptr_t in a lot of places where T* used; this could be used
here, by adding:
template<>
bool is_pointer( std::nullptr_t )
{
return true ;
}
Things like boost::shared_ptr< T > may also require a special
overload.
}
On a related note, I'm not in love with any of the common
expressions returned from ::main, so I generally take
advantage of the fact that we're allowed to omit any such
expression. Here are the alternatives I've considered:
int main() {
/* There should be no need for the macro. */
// return EXIT_SUCCESS;
/* There should be no need for the integer literal. */
// return 0;
/* "Return the ordinary, default value of my return type." Makes
* sense, though sufficiently unfamiliar to most developers that
* it may initially hurt readability.
*/
// return int( );
/* Arguably ideal, though verbose. Self-documenting. */
typedef int result_type;
result_type const exit_success = result_type( );
return exit_success;
}
And how do you return failure?
What I'll usually do is:
return ProgramStatus::instance().returnCode() ;
The actual value returned will depend on which calls to
ProgramStatus::instance().setError( gravity ) ...
have been made. Somewhere deep down in there, there's an enum,
which is mapped in a platform dependent manner to the actual
return code, with the default mapping using EXIT_SUCCESS and
EXIT_FAILURE (but neither Unix nor Windows use the default
mapping---no point in throwing information away). Anyway, the
fact remains that you need more than one value.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34