Re: A style question on const char* vs. std::string
Zeljko Vrba wrote:
Hello! Consider a hypothetical situation:
std::map<type_a, const char*> some_map;
type_a is an enum type. The some_map is filled with strings on program
startup; it is supposed to be 1:1 mapping for each enum element. I am aware
that this is better done with a static array of strings or
boost::array. However, this is not at the core of the question, so please bear
with me.
Just a question, but would it make sense for the object to be
const. (I typically use something like this for mapping enum
values to their names; in such cases, the map is declared
const.)
Another person has proposed to use std::map<type_a, std::string>, one of the
arguments being that "it's safer".
It's very definitely safer if you are modifying the entries,
adding, changing or removing them. If the map is const,
however, and the values are all string literals to begin with, I
don't see what it buys you.
Namely, referencing an unitialized element
in the map (some_map[blah]) will insert and return a default-constructed value
type.
If the mapping is supposed to be 1:1, you shouldn't be using
operator[] on the map. It's that simple. (If the map is const,
you can't use it. Which is even simpler:-).)
I'd suggest wrapping the map somehow, so that the user doesn't
even have to see it. What's its functional role? Maybe a
global function along the lines of getName (which can, of
course, be overloaded for many different enum types) would be
appropriate. Something like:
char const*
getName( EnumType value )
{
std::map< EnumType, char const* >::const_iterator
elem = map.find( value ) ;
if ( elem == map.end() ) {
// You get to choose your error handling!!!
// From what you say, aborting might be best,
// and you can replace the if with an assert,
// but if you later decide for exceptions, or
// returning a null pointer, it's no real problem
}
return elem.second ;
}
I find myself doing a lot of this. std::map tries to be
generally useful, for all cases, and what you want to do if the
element isn't present tends to vary a lot.
When the value type is const char*, its default is NULL, and this is
what gets returned to the caller, resulting in a crash as soon as the value is
used. Since the mapping is supposed to be 1:1, and referencing an
uninitialized element is a programmer's error, I believe that crashing is a
GOOD thing and an early indication of faulty program.
I have in addition claimed that using std::string (in this particular
scenario, NOT in general!) would actually hide errors and push them deeper in
the program logic where they would be harder to discover. I argument this on
the grounds that std::string will silently insert and return an empty string
to the caller. This not only inserts a value in the map that logically should
not be there (remember, it's a map from an enum type to a list of constant
strings), but it does not signal it in any visible way. Empty string might
eventually cause program malfunction in a more subtle way, thus potentially
hiding bugs for a long time.
I agree with your reasoning here, but if the map object is
const, you don't have to worry about accidentally creating an
additional entry.
Note that if you do want to return NULL in case the element
isn't present, there's nothing wrong in general with having a
map with std::string* in it. If you want to maintain a 1:1
relationship with an enum, on the other hand, it's definitly not
the way to go.
Another concern that I expressed is adding a new value to enum, but forgetting
to update the map. It is another problem that using std::string would hide.
Hmmm. In my case, this is automatically generated code; if I
touch the header containing the enum, the make file
automatically regenerates the map so that it is up to date.
This only works if there is a direct mapping between the names
of the enum types and the strings in the map, of course.
Otherwise, I'd probably use a simple two column text file, with
the value names in the first column, and the corresponding
strings in the second, and generate both the enum and the map
automatically.
This another person has said that I do not understand "the fundamental
concepts of the language" (referring to C++), that "I'm welcome to continue to
write C in C++ if I like so", and has referred me to the following link:
http://www.parashift.com/c++-faq-lite/containers.html#faq-34.1
Apart from that he gave no other argumentation for why using std::string would
be good.
Can you provide some guidelines?
Ask them what happens if the object is to be used in the
constructor of a static object:-). (In fact, my normal
implementation uses linear search in an array of
struct { EnumType, char const* } ;
Static initialization is the surest way of avoiding order of
initialization problems.)
In general, I'd be leary about people saying things like the
above, at least if they're basing it on one single case. If
you systematically use char* instead of std::string, of course,
the case would be different. But in this case, I'd say that
you've actually understood the language better than they have.
While I'm not sure that I'd really consider it a feature, the
language has two different string types, and while one is very
general, and should be preferred in most cases, string literals
have the other type, and only the other type can be statically
initialized. If your strings are all in fact string literals,
you might point out that you're mapping between two compile time
constants, and there are no compile time constants of type
std::string.
And if they don't understand what a compile time constant is,
I'd say that they're the ones who don't understand the
fundamental concepts of the language.
--
James Kanze (GABI Software) email:james.kanze@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
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]