[C++] Proper enum usage

Started by
19 comments, last by Antheus 14 years, 1 month ago
Hello GameDevs ! I really love the clear visualization of enums but I'm not entirely sure how it should be used properly. What I see is an opportunity to manage indices with easily editable text labels, so what I've done is declare enums for things like for instance in my application "error message grades".

enum ERRORGRADE
{
	NONCRITICAL,	// Does not terminate the program
	TERMINATE,		// Terminates the program
	TERMINATEANDMSG	// Terminates the program and shows a message box
};

The declaration is stored within the header file for the CError class, and whenever I call the method Error I pass the enum element appropriate for the specific error.

void Error( std::string file, int line, ERRORGRADE grade, std::string text1 = "", std::string text2 = "" );

Now to the actual "problem". I am able to pass the elements by themselves without any hassle, but I would very much like to be explicit and pass the argument as such ERRORGRADE::NONCRITICAL to avoid passing a bad keyword (i.e. accidently using an element from a different enum). This always results in a warning in Visual Studio 2008 (warning C4482: nonstandard extension used: enum 'ERRORGRADE' used in qualified name) and it looks very bad after a whole lot of function calls. So my question is: am I using enumerators in a proper way and is there some way to call the elements through the enum scope without getting all those warnings? Many thanks in advance ! - Dave
Advertisement
The way you are using enums now is fine. Think of them as defining a type for which its values are from a finite set of distinct elements that are explicitly enumerated.

For your second question, enums do not have their own scope. You could put them inside another scope (like a class's), or use a namespace instead (with the side-effect of it requiring a slightly longer identifier for the type):

namespace ErrorGrade {    enum Enum {	NONCRITICAL,	// Does not terminate the program	TERMINATE,	// Terminates the program	TERMINATEANDMSG	// Terminates the program and shows a message box    };}ErrorGrade::Enum grade = ErrorGrade::TERMINATE;
I'm fairly sure this is a problem they are addressing in C++0x. Right now enums aren't even type-safe, so I presume they would add something like scope resolution for enum members.
Quote:Original post by Normandy
I'm fairly sure this is a problem they are addressing in C++0x. Right now enums aren't even type-safe, so I presume they would add something like scope resolution for enum members.

C++0x doesn't fix the issue, it just sidesteps the issue by introducing a new type of enumeration, the "enum class" type. The enum class type is type safe, although you can use explicit casting to convert to integral type..

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Enum to int mapping is conceptually undefined, even though many languages support one way or another. Enum 'ERROR' is just that - 'ERROR'. If you choose to assign it a numeric value, the mapping is up to you. Enums conceptually also do not have relation between values. ERROR is different from WARNING, yet neither is greater as far as enums go.

Even without taking C++'s int/enum equivalence, your use of enums is not suitable for the purpose.

IMHO, the following is how the API should be used in this particular case:
void Error( std::string file, int line, std::string text1 = "", std::string text2 = "", ErrorGrade g = ErrorGrade::None );...Error("File.cpp", 17, "", "");Error("File.cpp", 17, "", ErrorGrade::Terminate);Error("File.cpp", 17, "", ErrorGrade::Terminate || ErrorGrade::MessageBox);


How the above is implemented depends. Values could be simple ints, or they could be more complex classes. The ErrorGrade above can be polymorphic as well, implementing the error behavior by itself. This design is more representative for values which can be combined (noncritical = 00, terminate = 01, terminate+box = 11, consequently just box = 10).

Alternatively, if grades are ordered, then providing a wrapper that defines order, as well as strong type safety will be better solution.

Given real world use, I'm consistently disappointed with enums and can't think of many uses for them, even in other languages. They reek too much of hardcoding. If they define functionality, then there are other ways to implement it (polymorphism, parameters). If they define data, then the data will often be best served from outside. And if for error or labeling, then const int values do the job, even if wrapped in some class.
Quote:Original post by Antheus
And if for error or labeling, then const int values do the job, even if wrapped in some class.


But...an enum IS an integer! It just gives a cute name for a number. People forget numbers, names not so quickly. It's actually really handy, the enum, though its a pity that C++ seems to go so difficult about it. I have to do a lot of casting when I add an integer to an enum value. Also the way we can't define the type for the integer is a pity, as well as it's not encapsulated like a class.

I do agree that most uses of enums are best from outside, though I still prefer something that names my integers. Would be better if we could 'append' enums too, where all elements of 2 enums have unique values. In other words, make it easy for library users to extend, for example add new events. Sure, you can start from the end of the last, but what if you have 3 enums? Just getting garbage.

If 'TERMINATEANDMSG' equals 2, then why is 'TERMINATEANDMSG' - 1 not just 1 and thus 'TERMINATE'? Because it is, except that C++ wants you to add a bunch of needless castings.
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Quote:Original post by Decrius
But...an enum IS an integer!
Nope, an enum is just a name. That C/C++ choose to implement them in terms of integers is immaterial.
Quote:It just gives a cute name for a number.
Nope, as Antheus already mentioned, const int is how one applies a cute name to a number in C/C++.
Quote:***truncated***
And those are all problems which using const int instead of enum solves in one fell swoop.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by Decrius

It just gives a cute name for a number. People forget numbers, names not so quickly.


As soon as you introduce ordering or treat them as ordinal types, you open a new can of bugs. Such as: ERROR + WARNING = FILE_NOT_FOUND, while WARNING * ERROR = EXIT and EXIT - FILE_NOT_FOUND + WARNING = DEFAULT.

And if treating them as bitmasks, the following problems arise:
LEFT,RIGHT,TOP,BOTTOM,LEFT_BUTTON,RIGHT_BUTTONDEVICE_OFF

Left, right, top, bottom is the direction of stick.
Buttons can be pressed at any time.
Device off means other values must be 0 or unspecified.

But this is what one uses in actual design, and enums fail to address any of those issues.
Working on reasonably old code here (15 or so years in some places), and there are many places where plain integers are passed to and returned from functions to indicate things, with a (usually) outdated, incorrect comment explaining what that value meant there. Sometimes 2 or 3 values are used for the same thing, and sometimes the same value is used more than once for different things.

Replacing those with enums (NOT const ints) has been very helpful because now when building the compiler gives me errors when just passing in integers, and some well-hidden bugs are being removed. Though not completely type-safe, just the fact that it doesn't let ints pretend to be enums (without some explicit casting), and the choices are limited to only what's valid, (and can't use cross-enums either) has been very, VERY helpful. With hundreds, maybe thousands of constant identifiers (whether #defined or const int'ed or enumed) it's also very helpful to know which set of identifiers I will be choosing from, rather than try to decypher a bunch of code and #defines which are poorly named in the first place.

These are cases where the actual value of the identifier is really irrelevant, but they just need to be unique. And we don't want to use a CompressionType where we want to know which HlsBand we are using. Are these not good candidates for enums??
Well, talking on the usage side of things:
There are almost always alternatives to the usage of named integers or enumerations.

The question becomes: Is it worth it? In the end the answer is "sometimes yes, sometimes no." More succinctly: "Maybe."

This depends heavily on your current design, on the goals of your project, on the technologies employed by your project, and many other issues as well.

The example used, which is one that deals with errors, seems a bit... wonky. I would never have an enumeration for my error severity. In fact, I'm not entirely certain what the code is trying to convey. Lets look at a few questions about the demonstration code:
Is the Error function exit-able, or does entrance to the function pose the potential to halt execution within the function?

Does the behavior of the Error function radically change depending on the parameters passed in?

Why aren't you using exceptions (and hence RAII)?

Depending on the answers to these questions your usage of an enumeration could be entirely...stupid. As an example: If the behavior of the function changes radically based on the ERRORGRADE enumeration, then you shouldn't have those behaviors all bound up in one function, but across a number of functions. If the function has the potential to exit the application without returning, then are you properly dealing with scoped objects? Less you forget: If the function never returns then some objects whose destructors would normally be called will not be. This means open files may be left dangling, sockets will abort mysteriously, and other resources you have allocated can be left in a dangling or unknown state (such as named mutexes).

Then there is the question of "Why is this in a class?" Depending on your usage of the class it may or may not be appropriate to have these functions in a class. If your simply calling these methods as single shot calls, then there's no point in using a class, free functions will be cleaner.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

This topic is closed to new replies.

Advertisement