• Advertisement
Sign in to follow this  

Unity [C++] Proper enum usage

This topic is 2928 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
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;

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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..

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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_BUTTON
DEVICE_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.

Share this post


Link to post
Share on other sites
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??

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Wow, thank you guys for those very interesting reflections. If I understand things correctly: it should not cause me problems to just pass one enum element as an argument in my custom functions and use for comparison with the same enum elements as long as they are within the same program on the same machine?

So far I have only checked for equivalence as such:


if( grade == ERRORGRADE::TERMINATE )
{
PostQuitMessage( 0 );
}
else if( grade == ERRORGRADE::TERMINATEANDMSG )
{
std::string message = text1 + text2;
MessageBox( 0, message.c_str(), "Critical Error", MB_OK | MB_ICONSTOP | MB_APPLMODAL );
PostQuitMessage( 0 );
}



I like the idea of using an enum to control a switch, since it seems to accept the enum as an integral variable. Is this a situation where its usage might be considered unsafe?

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
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??


If you get a consensus across all developers that enums are not ints, and that there is no relation between those, unless via from_int/to_int, then they are suitable.

In practice and for historical, practical or cultural reasons, enums and ints are used interchangeably, offering no advantage.

As with everything in C++, one needs to settle on a subset of features to be productive. In case of enums, treating them as named ints is not beneficial, despite language not only allows it, but historically design was in favor of it.

Otherwise, code will rot over time, which brings us full circle. At some point, someone will simply need to use an int instead of an enum, and will force one into another.

Hence, strongly typed values are likely better, but they add complexity and verbosity. They can however result in perfectly safe code, which is still convenient to use.

It's just the age-old C++ issue, jack of all trades, master of none. It can be trained, but requires considerable upfront work on boiler-plate functionality.

Share this post


Link to post
Share on other sites
@Washu
I feel perhaps I should explain this particular implementation a bit better.

The whole application is built upon classes that return either success or failure when they are initiated, so should any call return false it will attempt to close down along the "function call branch" back into the main loop which would end seeing as a quit message was posted.

I'm sure this isn't the most fool-proof implementation, but all I really want to do with the enum is to provide myself with a lazy way of managing what each individual error call will do as I manually write the calls into the rest of the code.

I only really want critical errors at the "end" of a function call branch to display a message box, to let the user know what really caused all the trouble. The rest of the error calls should only log the error in a text file.

This probably washes out the discussion a bit but I really appreciate all of your concern :)

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
Nope, an enum is just a name. That C/C++ choose to implement them in terms of integers is immaterial.


This is true, but it's also silly. In my mind, the most intuitive use for something like an enum is to simply be a wrapper around such const int functionality. Enums SHOULD be the same as const ints. Conceptually, there's really no reason to ever use enums... in fact, they're kind of a trap. For what you would intuitively think they're useful for, they are actually undefined :(

Share this post


Link to post
Share on other sites
Quote:
Original post by Vanderry

The whole application is built upon classes that return either success or failure when they are initiated, so should any call return false it will attempt to close down along the "function call branch" back into the main loop which would end seeing as a quit message was posted.

This is what exceptions do.

Quote:
I only really want critical errors at the "end" of a function call branch to display a message box, to let the user know what really caused all the trouble. The rest of the error calls should only log the error in a text file.


These are two different things, one is logging, the other are fatal errors, so they can't be handled in same way.

try {
while (...) {
// main loop stuff
log(INFO, "Hello world"); // don't quit, just log

try {
load_stuff();
} catch (file_not_found_exception & e) {
log(WARNING, "It's ok, file might not exist");
}
}
} catch (std::exception & e) {
// unhandled exception, nobody caught it, we can't handle it, let's quit
PostQuitMessage( 0 );
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Washu
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..


I guess it depends what you mean by "C++0x doesn't fix the issue", but it does indeed cause the values to be scoped to the defining enum class, solving the problem the OP asked about.

Share this post


Link to post
Share on other sites
Doesn't C++0x also allow you to specify the underlying type of the enum, including allowing non-integer types?

Share this post


Link to post
Share on other sites
Quote:
Original post by theOcelot
Doesn't C++0x also allow you to specify the underlying type of the enum, including allowing non-integer types?


I hadn't heard this. So you could define an enum as being a string?

Share this post


Link to post
Share on other sites
Quote:
Original post by jwezorek
Quote:
Original post by theOcelot
Doesn't C++0x also allow you to specify the underlying type of the enum, including allowing non-integer types?


I hadn't heard this. So you could define an enum as being a string?


It actually does still require integral types. Any integral type except wchar_t is suitable.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf

See section 3.1 specifically.

Share this post


Link to post
Share on other sites
I must say that I'm a bit surprised so many of you oppose the use of enums when it seems so widely used, not the least in many professional DirectX-applications I have studied. I guess that might be one of those fields where their pros outweigh their cons.

Share this post


Link to post
Share on other sites
Quote:
Original post by Vanderry
I must say that I'm a bit surprised so many of you oppose the use of enums when it seems so widely used, not the least in many professional DirectX-applications I have studied. I guess that might be one of those fields where their pros outweigh their cons.


I'm only opposed towards treating enums and ints as equivalent, or using them as typesafe replacement for numeric constants.

At some point, they need to be written somewhere, and then there's that old mapping problem again, and the first thing will be assuming literal int interpretation. But then this is written externally, code changes, and suddenly old values no longer make sense, yet there is no check in place, or even possible.

And not just C or C++. For example, before 1.5/5, Java lacked enum type, so a cookie-cutter version was patternized. Except that the implementation (static/singletonish-based one even) that was de-facto standard had some obscure serialization/initialization issues which would result in same enum labels having different values, sometimes even inside same VM when serialization was involved.

IMHO - either full static type safety, or full dynamic language. The things in between tend to annoying in the long term, even if over short-term a tool, perhaps an inference type analyzer can help a bit.

This is especially true for C++ under modern compilers which are good at eliminating unused code, or minimizing memory layout of such convenience wrappers.

While likely overbloated and over-engineered, perhaps look at boost::error's design.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Manuel Berger
      Hello fellow devs!
      Once again I started working on an 2D adventure game and right now I'm doing the character-movement/animation. I'm not a big math guy and I was happy about my solution, but soon I realized that it's flawed.
      My player has 5 walking-animations, mirrored for the left side: up, upright, right, downright, down. With the atan2 function I get the angle between player and destination. To get an index from 0 to 4, I divide PI by 5 and see how many times it goes into the player-destination angle.

      In Pseudo-Code:
      angle = atan2(destination.x - player.x, destination.y - player.y) //swapped y and x to get mirrored angle around the y axis
      index = (int) (angle / (PI / 5));
      PlayAnimation(index); //0 = up, 1 = up_right, 2 = right, 3 = down_right, 4 = down

      Besides the fact that when angle is equal to PI it produces an index of 5, this works like a charm. Or at least I thought so at first. When I tested it, I realized that the up and down animation is playing more often than the others, which is pretty logical, since they have double the angle.

      What I'm trying to achieve is something like this, but with equal angles, so that up and down has the same range as all other directions.

      I can't get my head around it. Any suggestions? Is the whole approach doomed?

      Thank you in advance for any input!
       
    • By devbyskc
      Hi Everyone,
      Like most here, I'm a newbie but have been dabbling with game development for a few years. I am currently working full-time overseas and learning the craft in my spare time. It's been a long but highly rewarding adventure. Much of my time has been spent working through tutorials. In all of them, as well as my own attempts at development, I used the audio files supplied by the tutorial author, or obtained from one of the numerous sites online. I am working solo, and will be for a while, so I don't want to get too wrapped up with any one skill set. Regarding audio, the files I've found and used are good for what I was doing at the time. However I would now like to try my hand at customizing the audio more. My game engine of choice is Unity and it has an audio mixer built in that I have experimented with following their tutorials. I have obtained a great book called Game Audio Development with Unity 5.x that I am working through. Half way through the book it introduces using FMOD to supplement the Unity Audio Mixer. Later in the book, the author introduces Reaper (a very popular DAW) as an external program to compose and mix music to be integrated with Unity. I did some research on DAWs and quickly became overwhelmed. Much of what I found was geared toward professional sound engineers and sound designers. I am in no way trying or even thinking about getting to that level. All I want to be able to do is take a music file, and tweak it some to get the sound I want for my game. I've played with Audacity as well, but it didn't seem to fit the bill. So that is why I am looking at a better quality DAW. Since being solo, I am also under a budget contraint. So of all the DAW software out there, I am considering Reaper or Presonus Studio One due to their pricing. My question is, is investing the time to learn about using a DAW to tweak a sound file worth it? Are there any solo developers currently using a DAW as part of their overall workflow? If so, which one? I've also come across Fabric which is a Unity plug-in that enhances the built-in audio mixer. Would that be a better alternative?
      I know this is long, and maybe I haven't communicated well in trying to be brief. But any advice from the gurus/vets would be greatly appreciated. I've leaned so much and had a lot of fun in the process. BTW, I am also a senior citizen (I cut my programming teeth back using punch cards and Structured Basic when it first came out). If anyone needs more clarification of what I am trying to accomplish please let me know.  Thanks in advance for any assistance/advice.
    • By Yosef BenSadon
      Hi , I was considering this start up http://adshir.com/, for investment and i would like a little bit of feedback on what the developers community think about the technology.
      So far what they have is a demo that runs in real time on a Tablet at over 60FPS, it runs locally on the  integrated GPU of the i7 . They have a 20 000 triangles  dinosaur that looks impressive,  better than anything i saw on a mobile device, with reflections and shadows looking very close to what they would look in the real world. They achieved this thanks to a  new algorithm of a rendering technique called Path tracing/Ray tracing, that  is very demanding and so far it is done mostly for static images.
      From what i checked around there is no real option for real time ray tracing (60 FPS on consumer devices). There was imagination technologies that were supposed to release a chip that supports real time ray tracing, but i did not found they had a product in the market or even if the technology is finished as their last demo  i found was with a PC.  The other one is OTOY with their brigade engine that is still not released and if i understand well is more a cloud solution than in hardware solution .
      Would there  be a sizable  interest in the developers community in having such a product as a plug-in for existing game engines?  How important  is Ray tracing to the  future of high end real time graphics?
    • By bryandalo
      Good day,

      I just wanted to share our casual game that is available for android.

      Description: Fight your way from the ravenous plant monster for survival through flips. The rules are simple, drag and release your phone screen. Improve your skills and show it to your friends with the games quirky ranks. Select an array of characters using the orb you acquire throughout the game.

      Download: https://play.google.com/store/apps/details?id=com.HellmodeGames.FlipEscape&hl=en
       
      Trailer: 
       
    • By khawk
      Watch the latest from Unity.
       
  • Advertisement