I think 0 should be preferred and NULL should be avoided, but the other programmer says that using the macro
NULL in C++ is better, because it is easier to distinguish it.
Some of my arguments:
1. C++ standard mentions:
"A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to
zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that
type and is distinguishable from every other value of pointer to object or pointer to function type. Two null
pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to
cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a
qualification conversion (4.4)".
C standard mentions (from K&R2, page 198, ?6.6):
"An integral constant expression with value 0, or such an expression cast to type void *, may be converted, by
a cast, by assignment, or by comparison, to a pointer of any type. This produces a null pointer that is equal
to another null pointer of the same type, but unequal to any pointer to a function or object".
Also TC++PL3 mentions:
"5.1.1 Zero
Zero (0) is an int. Because of standard conversions (C.6.2.3), 0 can be used as a constant of any integral
(4.1.1), floating-point, pointer, or pointer-to-member type. The type of zero will be determined by context.
Zero will typically (but not necessarily) be represented by the bit pattern all-zeros of the appropriate size.
No object is allocated with the address 0. Consequently, 0 acts as a pointer literal, indicating that a
pointer doesn't refer to an object.
In C, it has been popular to define a macro NULL to represent the zero pointer. Because of C++'s tighter type
checking, the use of plain 0, rather than any suggested NULL macro, leads to fewer problems. If you feel you
must define NULL, use
const int NULL= 0;
The const qualifier (5.4) prevents accidental redefinition of NULL and ensures that NULL can be used where a
constant is required".
So in C++, the "natural" value denoting a null pointer is the integral value 0, while in C, the "natural"
value denoting a null pointer is the value that *results* from the conversion of the integral value 0 to the
type of the pointer.
2. The C++ standard says about the macro NULL:
" The macro NULL is an implementation-defined C++ null pointer constant in this International Standard (4.10)
*180.
*180 Possible definitions include 0 and 0L, but not (void*)0".
I think this implies that an implementation may define macro NULL with an implemetation-defined way, for
example a keyword:
#define NULL _somekeyword
3. Since in C NULL is *usually* defined as a pointer type (e.g. #define NULL ((void *)0) ), and in C++ NULL is
*usually* defined with the integral value 0, a programmer that knows C may easily think that NULL is of
pointer type, and be easily confused by the code:
#include <iostream>
#include <cstddef>
void f(void *) { std::cout<< "f(void *) was called.\n"; }
void f(int) { std::cout<< "f(int) was called.\n"; }
void f(long) { std::cout<< "f(long) was called.\n"; }
int main()
{
f(NULL);
}
which in my system produces:
john@john-laptop:~/Projects/anjuta/cpp/src$ g++ -ansi -pedantic-errors -Wall main.cc -o foobar
john@john-laptop:~/Projects/anjuta/cpp/src$ ./foobar
==> f(long) was called.
john@john-laptop:~/Projects/anjuta/cpp/src$
So I think using NULL is a bad practice n C++.
The other programmer considers using NULL in C++ a good practice, because:
1. Using NULL denotes a pointer, while 0 has more uses, and when you see NULL you easily know that you are
dealing with a pointer.
2. NULL is easier to search and find in the source code.
3. NULL is the definition of a constant, and it can easily be modified, so in upcoming C++0x can be easily
defined as #define NULL nullptr.
Also the mentality of C++0x, is more towards the way of the old NULL, rather than 0.
So, using NULL is NOT a bad style.
So, who is right?
Thanks,
--
Ioannis A. Vranos
C95 / C++03 Developer
> No object is allocated with the address 0. Consequently, 0 acts as a
> pointer literal, indicating that a pointer doesn't refer to an object.
AFAIK, this is not exactly exact. "address" is not a C notion. C has
a notion of pointer. The pointer 0 cannot refer any object. But it
doesn't mean that the microprocessor address 0 may not be used to
store a object. For example, AFAIK, a C compiler could map pointers
to addresses by subtracting a small constant, eg. sizeof(int). Then
if sizeof(int)==4, (*((char*)4))=42; would store 42 at the byte at
address 0.
> So I think using NULL is a bad practice n C++.
I agree. Nowadays, I tend to use 0 both in C and C++.
--
__Pascal Bourguignon__
This post is guaranteed to start a holy war.
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. 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;
#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';
}
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;
}
> 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...@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
> Ioannis Vranos <ivr...@freemail.gr> writes:
>
> > No object is allocated with the address 0. Consequently, 0 acts as a
> > pointer literal, indicating that a pointer doesn't refer to an object.
>
> AFAIK, this is not exactly exact. "address" is not a C notion. C has
> a notion of pointer. The pointer 0 cannot refer any object. But it
[snip]
That strikes me as a rather silly statement. Ever since ANSI 1989,
ISO 1990:
"The result of the unary & (address-of) operator is a pointer to the
object or function designated by its operand. If the operand has
type ''type'' the result has type ''pointer to rype.''"
This in a section named "Address and indirection operators".
--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
news:comp.lang.c http://c-faq.com/
news:comp.lang.c++ http://www.parashift.com/c++-faq-lite/
news:alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
It won't compile. nullptr is a keyword and not a name that's declared
in std:: namespace. Apart from that there will probably an ambiguity.
There are many operator<< overloads that take a pointer as parameter
and none of them is nullptr_t as far as I can tell.
Cheers!
SG
>> 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.
Ok, *now* you're dating yourself. :)
> 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* )",
One quickly tires of having to look up function signatures to see what
NULL is meant to represent in a given function call. What aren't you
passing, and why aren't you passing it?
/* I detest this. */
std::string const s = read_string(NULL);
/* I prefer: */
/* Somewhere near the top of the file, among configuration data. */
typedef char* buffer_pointer;
buffer_pointer const using_default_buffer = buffer_pointer( );
// ...
/* In some function body. */
std::string const s = read_string(using_default_buffer);
Anyway, I begin with the premise that pointer types are given local
typedefs, so the case of
some::long::name::that::defies::the::law::of::demeter::anyway* does not
arise. What appear often, and I admit is a pain in the neck, is the set
of typedefs at the top of some function or class definition:
typedef std::iterator_traits<Forward_iterator> traits;
typedef traits::pointer pointer;
typedef traits::reference reference;
// various types and constants...
I find this the lesser evil. YMMV.
> 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.)
I use pointer for actual types, and ptr for templates. That seems to be
in keeping with the STL and Boost conventions, e.g.
iterator_traits<whatever>::pointer, but shared_ptr.
> 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.
There's precious little benefit to hard-coding raw pointers, but a
strong up-side to using the typedefs consistently. I don't always know
ahead of time whether a smart pointer type will be needed in a
particular context. I'm also just more comfortable working at a higher
level of abstraction; I don't always want to know the details. It's
enough to know that I can look under the covers when the need arises.
>> /* 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?
result_type const exit_failure = !exit_success;
> 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,
I have no problem with that. You haven't hard-coded any literals, or
used any macros.
By the way, do you actually make use of the various return values after
the program returns? In the common case of using non-zero exit values
to indicate errors, my exit values are effectively boolean, with details
printed to stderr in natural language.
> 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).
THe platform-dependent values presumably come from platform-specific
headers, so still no hard-coded literals. If you do actually have to
write out the literals, I would expect them to be assigned to constants
with meaningful names, or at least to be accompanied by comments. The
platform headers may provide the expected values only as MACROS, but
c'est la vie, we live in an imperfect world.
> Anyway, the
> fact remains that you need more than one value.
Right, but those other values have meanings of their own, that should be
spelled out explicitly in the code. Your use of enums suggests that
you're already doing that.
> > I went that way for a short period of time. Not a
> > default-constructed rvalue---that didn't exist back then.
> Ok, *now* you're dating yourself. :)
I learned C++ from the first edition of Stroustrup. Back when
there weren't really nested classes, much less templates and
exceptions.
> /* I prefer: */
> // ...
As I said, I like the idea in theory. (Although I find the
typedef's that I've seen for this sort of thing more confusing
than anything else... why write somthing_or_other_pointer, when
somthing_or_other* says exactly what you mean. Not to mention
the confusion which can occur when you're dealing with pointers
to both const and non-const.) In practice, my experience seems
to coincide with that of the authors of languages with stricter
type checking (Pascal and its descendants, including Ada): you
do want to distinguish "null pointers" from integers, etc., but
it doesn't seem really necessary to distinguish the actual type
of a null pointer; there's a small advantage in doing so, but
not enough to justify the extra effort. But you're at the
limits, and I can easily understand your position.
(Technically, of course, one could argue that a null pointer
should be a different type: a T* points to a T, and a null
pointer doesn't point to anything, so it can't have type T*.
Except, of course, when you assign it to a T*, it will have type
T*, so the argument is IMHO fatally flawed.)
> > 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.)
> I use pointer for actual types, and ptr for templates. That
> seems to be in keeping with the STL and Boost conventions,
> e.g. iterator_traits<whatever>::pointer, but shared_ptr.
Historically: it was auto_ptr in the standard, and ...::pointer
in the STL. The real difference here, however, is that in
...::pointer, pointer is the complete name of the type; in
auto_ptr, it's not.
> > 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.
> There's precious little benefit to hard-coding raw pointers,
> but a strong up-side to using the typedefs consistently. I
> don't always know ahead of time whether a smart pointer type
> will be needed in a particular context. I'm also just more
> comfortable working at a higher level of abstraction; I don't
> always want to know the details. It's enough to know that I
> can look under the covers when the need arises.
The fact that something is a pointer isn't really a detail. Of
course, you indicate this clearly in the name, so there's no
problem, but I still don't see where it buys that much.
> >> /* 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?
> result_type const exit_failure = !exit_success;
Except that that's not failure on some systems. For historical
reasons, you're dealing with an int, not a bool. As far as the
standard goes, there are three values you can use for that int
that have a meaning defined by the standard: 0, EXIT_SUCCESS and
EXIT_FAILURE (EXIT_SUCCESS may or may not be equal to 0).
Anything else is implementation defined.
> > 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,
> I have no problem with that. You haven't hard-coded any
> literals, or used any macros.
At the lowest levels, I have.
> By the way, do you actually make use of the various return
> values after the program returns? In the common case of using
> non-zero exit values to indicate errors, my exit values are
> effectively boolean, with details printed to stderr in natural
> language.
I do and I don't. Internally, I distinguish between comment,
warning, error, fatal and internal error. (When you output an
error with fatal status, ProgramStatus will exit immediately,
and when you output with internal error, ProgramStatus will
terminate using abort().) Under Unix (and Windows), I'll map
them to different values, although I'm not really happy about
the mapping---warning returns 1, where as it probably should
return 0, with some other means used to return a 1 (without a
message), so you can do something along the lines of grep (which
returns 1 if it doesn't find the pattern, and 2 for any real
errors).
Which is, of course, the crux of the issue. At the kernel
level, Unix doesn't have the notion of success/failure (unless
you consider a core dump failure). Generally, the convention is
anything other than 0 is failure (but this is a pure
Unix'ism---under VMS, odd is success, and even is failure, and
when you return 0, the runtime maps it to some odd value). But
I've seen more than a few shell scripts which expand $? and test
for more different possibilities.
> > 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).
> THe platform-dependent values presumably come from
> platform-specific headers, so still no hard-coded literals.
Sooner or later, there is a "1", "2", etc. But yes---a lot
later, in a platform-specific header. From
syst-posix/system.hh:
static char const preferredOptId = '-' ;
static char const allowedOptId[] = "-" ;
static char const altOptId = '+' ;
static char const asciiEsc = '\\' ;
static char const preferredDirSep = '/' ;
static char const allowedDirSep[] = "/" ;
static char const pathSep = ':' ;
static bool const ignoreCase = false ;
static char const stdinName[] = "-" ;
static char const defaultTmpDir[] = "/tmp" ;
static int const exitSuccess = 0 ;
static int const exitWarning = 1 ;
static int const exitError = 2 ;
static int const exitFatal = 3 ;
static int const exitInternal = 4 ;
syst-windows/system.hh obviously looks a little different, but
the exit codes are the same. For VMS, if I supported it, the
exit codes would also be different. And for systems I'm not
sure about, I'd map them to EXIT_SUCCESS and EXIT_FAILURE.
Note that the names are fairly abbreviated. This is very old
code, and while the names don't meet my current standards, I'm
not about to try and find all of the places they're used, to
replace them.
Also, these were all macros for a long time (and the header was
shared with C).
> If you do actually have to write out the literals, I would
> expect them to be assigned to constants with meaningful names,
> or at least to be accompanied by comments. The platform
> headers may provide the expected values only as MACROS, but
> c'est la vie, we live in an imperfect world.
The reason why EXIT_FAILURE and EXIT_SUCCESS are macros is
simple: the header that defines them is shared with C, and
macros are the only way to get a named constant in C.
> > Anyway, the fact remains that you need more than one value.
> Right, but those other values have meanings of their own, that
> should be spelled out explicitly in the code. Your use of
> enums suggests that you're already doing that.
Anytime I'm not returning success, I'm outputting an error
message to std::cerr as well. ProgramStatus has a member
function setError, which takes a "ProgramStatus::Gravity", and
returns an OutputStreamWrapper (so you can write:
ProgramStatus::instance().setError( ProgramStatus::error )
<< "error message with a value" << value ;
). If I do want to set a return status without outputting an
error message, there's also a setReturnCode function, but it's
little used. (If I had it to redo, I'd have warning map to
success as well, and invent some other code to return 1 in cases
like grep.)
> Ioannis Vranos <ivr...@freemail.gr> writes:
>
>> No object is allocated with the address 0. Consequently, 0 acts as a
>> pointer literal, indicating that a pointer doesn't refer to an object.
>
> AFAIK, this is not exactly exact. "address" is not a C notion.
Not right. Although the C standard doesn't define the meaning of the word
"address", that word is used a lot in the standard. Basically an address is
a pointer's value.
> C has a notion of pointer. The pointer 0 cannot refer any object. But it
> doesn't mean that the microprocessor address 0 may not be used to
> store a object.
An address doesn't even need to be a simple linear number. It can be
anything.
> For example, AFAIK, a C compiler could map pointers to addresses by
> subtracting a small constant, eg. sizeof(int).
The only mapping that you get here is the mapping between pointer and
integer if you convert from one to the other, and that mapping is
implementation-defined. However, that's not really relevant, because 0 is a
special value in a pointer context. The literal 0 is a pointer value, while
all other integer values are not.
Basically, in the following code, p1 and p2 may end up having different
values:
int i = 0;
void* p1 = (void*)0;
void* p2 = (void*)i;