Is There a Reason to Use Enum to Define a Single Constant in C++ Code

Is there a reason to use enum to define a single constant in C++ code?

The reason is mainly brevity. First of all, an enum can be anonymous:

 class foo {
enum { bar = 1 };
};

This effectively introduces bar as an integral constant. Note that the above is shorter than static const int.

Also, no-one could possibly write &bar if it's an enum member. If you do this:

 class foo {
static const int bar = 1;
}

and then the client of your class does this:

 printf("%p", &foo::bar);

then he will get a compile-time linker error that foo::bar is not defined (because, well, as an lvalue, it's not). In practice, with the Standard as it currently stands, anywhere bar is used where an integral constant expression is not required (i.e. where it is merely allowed), it requires an out-of-class definition of foo::bar. The places where such an expression is required are: enum initializers, case labels, array size in types (excepting new[]), and template arguments of integral types. Thus, using bar anywhere else technically requires a definition. See C++ Core Language Active Issue 712 for more info - there are no proposed resolutions as of yet.

In practice, most compilers these days are more lenient about this, and will let you get away with most "common sense" uses of static const int variables without requiring a definition. However, the corner cases may differ, however, so many consider it to be better to just use anonymous enum, for which everything is crystal clear, and there's no ambiguity at all.

Advantages in using an enum to define a single value? (C)

Enumeration constants have several advantages:

  • They're scoped and don't expand in contexts where they shouldn't (as pointed out by mafso).
  • Most debuggers are aware of them and can use them in expressions written in the debugger.

Macros have several different advantages:

  • They can be use in preprocessor conditionals (#if BITS_PER_WORD == 32 won't work if BITS_PER_WORD is an enumeration constant).
  • They can have arbitrary types (also covered in mafso's answer).
  • They can be removed (#undef) when no longer needed.

Why do people use enums in C++ as constants while they can use const?

An enumeration implies a set of related constants, so the added information about the relationship must be useful in their model of the problem at hand.

What makes a better constant in C, a macro or an enum?

In terms of readability, enumerations make better constants than macros, because related values are grouped together. In addition, enum defines a new type, so the readers of your program would have easier time figuring out what can be passed to the corresponding parameter.

Compare

#define UNKNOWN  0
#define SUNDAY 1
#define MONDAY 2
#define TUESDAY 3
...
#define SATURDAY 7

to

typedef enum {
UNKNOWN,
SUNDAY,
MONDAY,
TUESDAY,
...
SATURDAY,
} Weekday;

It is much easier to read code like this

void calendar_set_weekday(Weekday wd);

than this

void calendar_set_weekday(int wd);

because you know which constants it is OK to pass.

What is the use of enums in C and C++

Advantages over macros

  • Debuggers can print names for values
  • Your constants will be scoped (if you want, you can put them also in a namespace in C++)
  • The compiler can warn if you forget an enum constant in a switch
  • Values for the constants are automatically assigned, if you don't give explicit values
  • The enum type is always large enough to hold all the constants. When using #define, you have to commit to int or some typedef and ensure that all the constants fit manually

Should I use #define, enum or const?

Combine the strategies to reduce the disadvantages of a single approach. I work in embedded systems so the following solution is based on the fact that integer and bitwise operators are fast, low memory & low in flash usage.

Place the enum in a namespace to prevent the constants from polluting the global namespace.

namespace RecordType {

An enum declares and defines a compile time checked typed. Always use compile time type checking to make sure arguments and variables are given the correct type. There is no need for the typedef in C++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Create another member for an invalid state. This can be useful as error code; for example, when you want to return the state but the I/O operation fails. It is also useful for debugging; use it in initialisation lists and destructors to know if the variable's value should be used.

xInvalid = 16 };

Consider that you have two purposes for this type. To track the current state of a record and to create a mask to select records in certain states. Create an inline function to test if the value of the type is valid for your purpose; as a state marker vs a state mask. This will catch bugs as the typedef is just an int and a value such as 0xDEADBEEF may be in your variable through uninitialised or mispointed variables.

inline bool IsValidState( TRecordType v) {
switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
return false;
}

inline bool IsValidMask( TRecordType v) {
return v >= xNew && v < xInvalid ;
}

Add a using directive if you want to use the type often.

using RecordType ::TRecordType ;

The value checking functions are useful in asserts to trap bad values as soon as they are used. The quicker you catch a bug when running, the less damage it can do.

Here are some examples to put it all together.

void showRecords(TRecordType mask) {
assert(RecordType::IsValidMask(mask));
// do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
assert(RecordType::IsValidState(state));
if (RecordType ::xNew) {
// ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
assert(RecordType::IsValidState(newstate));
//...
if (! access_was_successful) return RecordType ::xInvalid;
return newstate;
}

The only way to ensure correct value safety is to use a dedicated class with operator overloads and that is left as an exercise for another reader.

Advantage and disadvantages of #define vs. constants?

Constants allow you to specify a datatype, which is (usually) an advantage. Macros are much more flexible, and therefore can get you into much more trouble if you're not careful.

Best practice is to use constants as much as possible, and use #define only when you really need a macro, not just a named literal value.

#define or enum?

Since the states are related elements I think is better to have an enum defining them.

Is there no problem using enumeration without declaration of enum variable in C?

Yes, enums are basically int replacement. However, there are some best practice concerns with the code you have posted. The question is borderline subjective, although it is widely recognized as good practice to avoid "magic numbers" in C programming.

That is, you shouldn't write a function that returns an int with either value 0, 1 or 2. That would be a rather cryptic interface. But replacing those "magic numbers" with an enum without changing the return type is sloppy. It should not return int, but instead instead you should have used a typedef like:

typedef enum { WIN, LOSE, DRAW } game_result_t;
...
game_result_t play_game(...)

This gives you a special type only used for this specific purpose. It improves readability and (arguably) adds a tiny bit of type safety.

Better yet, prefix the enum values, the function, everything in the same way, so that the reader can tell which items that belong together:

// game.h

typedef enum { GAME_WIN, GAME_LOSE, GAME_DRAW } game_result_t;
...
game_result_t game_play (...)

Now everything in game.h is prefixed game_, which makes it easy to find out where a certain constant or function came from, without having to dig/search through the code.



Related Topics



Leave a reply



Submit