Re: Macro NULL or 0
On May 26, 3:55 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
James Kanze wrote:
On May 25, 3:17 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
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. :)
I learned C++ from the first edition of Stroustrup. Back when
there weren't really nested classes, much less templates and
exceptions.
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.
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.)
--
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