As I've been reconstructing the networking code, I've added a very pretty object printing mechanism that can be compiled to zero-impact if desired (perhaps based upon rather or not DEBUG, DEBUG_NETWORK or some such macro is defined). I want this in place so that very good tracing can be done to simplify and easy network protocol troubleshooting. I suppose that something similar could be done by defining this for wireshark in whatever language you use to define this stuff, but I really think it's better to be able to do it from the program its self, especially since it keeps it bound to the code (there's no dual maintenance to worry about).
So the new networking stuff uses a lot of enum values and when printing an object, I would prefer to use the actual value name rather than printing a numeric value. This leads me back to an issue that's often bothered me, both in C or C++ and in Java, and that's printing the names of enum values. The solution I've been using (a very common one) is to define separately the enum body and a const char *[] array with the names like this:
Header File:
enum State {
STATE_UNCONNECTED, /** not yet connected */
STATE_LISTENING, /** not yet connected, but listening for connections */
STATE_CONNECTED, /** established a connection and sent (or sending) handshake */
STATE_INTRODUCED, /** handshake completed */
STATE_INITIALIZED, /** has received game settings (still in lobby) */
STATE_LAUNCH_READY, /** ready to launch game (when given the O.K.) */
STATE_LAUNCHING, /** launching game */
STATE_READY, /** ready to begin play */
STATE_PLAY, /** game started (normal play state) */
STATE_PAUSED, /** game paused */
STATE_QUIT, /** quit game requested/initiated */
STATE_END, /** game terminated */
STATE_COUNT
};
extern const char *StateEnumNames[STATE_COUNT];
Implementation File:
const char *StateEnumNames[STATE_COUNT] = {
"STATE_UNCONNECTED",
"STATE_LISTENING",
"STATE_CONNECTED",
"STATE_INTRODUCED",
"STATE_INITIALIZED",
"STATE_LAUNCH_READY",
"STATE_LAUNCHING",
"STATE_READY",
"STATE_PLAY",
"STATE_PAUSED",
"STATE_QUIT",
"STATE_END"
};
As you may be aware, code duplication really bothers me. If something has copy and paste code in it, then there should probably be a generic implementation somewhere, rather using templates, base classes, etc., to encapsulate the commonalities (just gotta be aware of template code bloat issues). I really hate having something like this where one depends upon the other because it's easy to screw up. I've been examining the alternatives and I have one, but I'm not sure I like it. So I ask you guy's opinion (and I intend this question for C/C++ programmers only please. But trust me, I value all of you guy's opinions where it counts!)
/*
This is a sample way to implement an enum so that the list only appears once in the code using
a preprocessor macro and that same list is used for both the enum body and a string array
that contains the corresponding name of each value.
Advantages
* No room for error
o Renaming any of the values automatically renames in both the enum body and the string array
o Reordering values automatically updated in both
* Don't have to have redundant prefix appended to each value name
* Less code text (no copy & paste)
* No need to maintain both enum body and string array separately
Disadvantages
* Not very Doxygen friendly (see notes below)
* Relies heavily on C preprocessor
* Added code complexity
* Is not immediately intutive (until you get used to it)
* It adds one more project-specific paradigm for the new programmer to learn
Other Notes
The use of "static const char *[]" is wasteful as (I believe) it would cause strings to be
embedded in every object file that includes them. I'm mostly using it here to keep all of this
code in one place. Most probably, the correct solution is to use "extern const char *[]" and
then use an implementation file to expand the names. However, if it were used for debug only
(i.e., had #ifdef DEBUG around it) the overhead wouldn't matter and it could be kept all
together.
Finally, it may be possible to get Doxygen to like this by feeding it MACRO_EXPANSION=YES,
EXPAND_ONLY_PREDEF=YES, and EXPAND_AS_DEFINED=<list> where <list> contains "EV" as well as
each of the xxx_ENUM_VALUES macros used.
*/
#define STATE_ENUM_VALUES \
EV(UNCONNECTED) /** not yet connected */ \
EV(LISTENING) /** not yet connected, but listening for connections */ \
EV(CONNECTED) /** established a connection and sent (or sending) handshake */ \
EV(INTRODUCED) /** handshake completed */ \
EV(INITIALIZED) /** has received game settings (still in lobby) */ \
EV(LAUNCH_READY)/** ready to launch game (when given the O.K.) */ \
EV(LAUNCHING) /** launching game */ \
EV(READY) /** ready to begin play */ \
EV(PLAY) /** game started (normal play state) */ \
EV(PAUSED) /** game paused */ \
EV(QUIT) /** quit game requested/initiated */ \
EV(END) /** game terminated*/
enum State {
#define EV(value_name) STATE_ ## value_name,
STATE_ENUM_VALUES
#undef EV
STATE_COUNT
};
static const char * StateEnumNames[STATE_COUNT] = {
#define EV(value_name) "STATE_" #value_name,
STATE_ENUM_VALUES
#undef EV
};
This is the simplest that I've been able to refine it to. It *might* be possible to further refine it with a generic macro that expands the enum and string array, but I actually don't know how to do it because I would have to redefine EV and I don't believe there is any way to undefine and redefine macros within a macro expansion, plus the fact that it would further obfuscate it. If it was possible, it would look something like this:
#ifdef SOME_DEBUG_OPTION
# define STRINGY_ENUM_MACRO(name, prefix, values) \
enum name { /* insert magic here */, prefix ## COUNT}; \
static const char *name ## EnumNames[prefix ## COUNT] = { /* insert magic here */ };
#else
# define STRINGY_ENUM_MACRO(name, prefix, values) \
enum name { /* insert magic here */, prefix ## COUNT};
#endif
#define STATE_ENUM_VALUES /* same as above */
STRINGY_ENUM_MACRO(State, STATE_, STATE_ENUM_VALUES)
If that's possible, and I don't know how to do it (thus the /* insert magic here */), then it would further reduce the number of lines of code (and at this point, there are some 20-ish similar enums in the code) but as I said, it would further obfuscate it.
For now, I'm going the copy & paste route, but I would love to hear your opinions. Thanks!