Jump to content

  • Log In with Google      Sign In   
  • Create Account


Enumeration vs #define


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
14 replies to this topic

#1 Dbproguy   Members   -  Reputation: 108

Like
0Likes
Like

Posted 05 November 2009 - 09:26 AM

So as I was reading C++ Primer as I always do these days... I ran across the enumerated type, and I've also known of #define for a while. I want to ask, what is the difference between the two? You can't change either as they are both constant as I understand, each one assigns a certain name to a specific number, but do they both have the same functionality? As usual I am sure the only difference is when you get into some really deep stuff and take advantage of the most advanced functionality, but I am still very curious. Thanks, Dbproguy

Sponsor:

#2 samoth   Crossbones+   -  Reputation: 4633

Like
0Likes
Like

Posted 05 November 2009 - 09:36 AM

#define is a text replacement, and enum is a type. Or, in other words, #define is dumb, and enum is smart.

Since a #define is only a text replacement, there is no checking for correctness. Enums add some error checking. For example, if you tell the compiler that a function takes a particular enum (even though the enum is only just an number internally, too), it will not accept any random number, but only a constant that is really an enum of that type. C++0x also has a new "enum class" type which is even more strict.

#3 samoth   Crossbones+   -  Reputation: 4633

Like
0Likes
Like

Posted 05 November 2009 - 09:56 AM

Maybe an example helps better...

If you #define SOME_VALUE 2<<24-1 and use SOME_VALUE a few times later in your source, then the preprocessor will just obtusely copy "2<<24-1" over every occurrance of SOME_VALUE. If there is a mistake in your macro definition or if there are some operators in the expression that have precedence (this happens often when people forget parentheses around macros), it is very possible that this expands to total bullshit. It might give compiler errors that you don't understand, or it might compile fine, but the program might not work properly.

Imagine for example:
if(a > SOME_VALUE && a < SOME_VALUE+1) {...}
What does this evaluate to? Not immediately obvious... though it incidentially works.

On the other hand, if you write enum blah{ some_value = 2<<24-1 }; then the compiler knows that some_value is a compile time constant with a numeric value that it will determine during the compilation (or, if there is an error, it will complain). It will also assign a type to it. Why is assigning a type useful?

Imagine this:
enum color { red, yellow, green };
...
void TrafficLight::Set(color to) { m_color = to; }
...
TrafficLight t;
t.Set(green); // works fine
t.Set(53); // does not compile, what's 53 supposed to be anyway?
#define blue 4
t.Set(blue); // does not compile either
t.Set(green+yellow); // does not compile either

The compiler knows that it may only accept certain valid names for this function

#4 janta   Members   -  Reputation: 345

Like
0Likes
Like

Posted 05 November 2009 - 10:00 AM

this is safe:
enum { constant = 10 + 3 };

this is not
#define constant 10 + 3

Redefining an enum will cause an error, redefining a macro, a simple warning.

In visual studio at least, during debugging you can get the numeric value of an enum, to that of a define.

#5 RandomBystander   Members   -  Reputation: 122

Like
0Likes
Like

Posted 05 November 2009 - 10:01 AM

Constants made via #define are simple text substitutions, and have no type per se.
Enums, on the other hand, are typesafe (every typedef enum defines a new type), and the compiler can proofread switch statements for unchecked values. AFAIK, they also optimize better than const ints. So...
typedef enum {
value1,
value2,
value3
} some_enum;

typedef enum {
valueA,
valueB,
valueC
} some_other_enum;

typedef enum {
valueArnie,
valueBertha,
valueC // Compiler generates an error here: we already used valueC in another enum
// #defines would have created a subtle bug here
} some_conflicting_enum;

void some_func(some_enum foo) {
switch(foo) {
case value1: /*blah*/ break;
case value2: /*more bla*/break;
// Compiler generates a warning here: value3 not used in switch statement
// use default statement to silence it
}
}

void some_bad_func(some_other_enum bar) {
some_func(bar); // Compiler generates a bonafide error
}


Also, one can have anonymous enums like this (I'll sneak my own question in here if you don't mind):

class MyClass {
private:
enum {state_uninit, state_ok, state_error } state;
/*....*/
public:
MyClass() {} // state holds which value now?
};

/*compare and contrast*/
#define STATE_UNINIT 0
#define STATE_OK 1
#define STATE_ERROR 2

class MyOtherClass {
private:
int state;
/*....*/
public:
MyOtherClass() {state=4;} // Compiler accepts this without complaining. Bad Compiler!
};


Unless you brutally abuse casts, enum variables are constrained in the values they can contain

#6 Rattenhirn   Crossbones+   -  Reputation: 1688

Like
0Likes
Like

Posted 05 November 2009 - 10:13 AM

Quote:
Original post by RandomBystanderAFAIK, they also optimize better than const ints.


What makes you think that?

Quote:
Original post by RandomBystander
Also, one can have anonymous enums like this (I'll sneak my own question in here if you don't mind):
*** Source Snippet Removed ***


If your sneaky question is, which value an uninitialized enum has, it's simple to answer: Any, very likely an invalid one.

An enum is a primitive type and needs to be initialized explicitly.

#7 terloon   Members   -  Reputation: 122

Like
0Likes
Like

Posted 05 November 2009 - 10:18 AM

The other difference, which I think is good design, is that they can be scope/belong to classes, giving more meaning to the data you are abstracting.

#8 Ravyne   Crossbones+   -  Reputation: 6883

Like
0Likes
Like

Posted 05 November 2009 - 10:25 AM

You were getting to the point, but it goes deeper than that.

A #define, used as a constant, is roughly equivilent to a C or C++ 'const' variable except, of course, that it is not typed, that it is inserted through text replacement, and that it's address cannot be taken via the & operator (because a #define never actually exists in memory at runtime).

An enumeration defines a category of related constants, which together constitute a new, distinct type. This means that type safety applies in full, and consequently that enumeration members from other enums and literal values cannot be assigned to the enum type, even when the constant values they represent may be identical to those in the target enumeration.

As an example, imagine that you wanted to represent the state of matter -- of which there are 4 generally accepted states: Solid, Liquid, Gas and Plasma.

With #define, you would do "#define SOLID 0", "#define LIQUID 1" and so on. Any function taking a "matter state" would be taking in some type which can be readily converted from an integer, and nothing prevents the programmer from passing SOLID to any such function, even when it has nothing to do with these "matter states" -- in other words you could very well multiply a Matrix by PLASMA, and the compiler is happy to make that happen, even though it makes no sense at all. Also just as bad, nothing prevents your functions that deal with the state of matter from taking any random value: What happens when one of those functions tries do deal with -1, or 17 (both of which coorespond to states of matter as yet undiscovered by science [grin])? And the final kicker is that you can't use the string "PLASMA" anywhere in your code, or it'll be replaced by "3" -- which is why such Macros are traditionally in all-caps to begin with; its simply a convention that helps avoid this shorcoming of the dumb text-replacement feature that is #define.

With enum, you would do (in C++):


enum eMatterState {
SOLID,
LIQUID,
GAS,
PLASMA
};





In C, you'd wrap that up in an appropriate typedef, IIRC. Anyhow, now your functions dealing with the state of matter can take a parameter of type "eMatterState", so it can only take one of SOLID, LIQUID, GAS or PLASMA -- There is no way to pass -1 or 17, or even 0, 1, 2, or 3 without going through the eMatterState type. Also, no more multiplying that Matrix by PLASMA, unless you specifically define such an operation taking eMatterState as its parameter.

In short, enums are a way to define a new type which represents a set of acceptable values (which may be contiguous or sparse) that the compiler uses to enforce type safety in much the same way as classes do in C++ -- enumerations can be though of as a "class" of related integer constants. This helps keep you, the programmer, from making silly mistakes by allowing the compiler to enforce not just type correctness, but logical correctness as well.

As someone else mentioned, since enums are primitive types, they must be initialized, and so they should always be. Uninitialized enums are the only way to represent a value in an enum which is outside the set of acceptable values. Well, aside from abusing typecasting.


#define should almost always be avoided, and should only be used in cases where it's "feature" of not being type-safe is actually useful. These cases are few and far between, and you should stick to const for stand-alone constant values, and enum for related constant values, whenever possible. Until you can understand clearly when such a use of #define is necessary, they typically pose more risk than they are worth in non-trivial use cases.

[Edited by - Ravyne on November 5, 2009 5:25:21 PM]

#9 Ravyne   Crossbones+   -  Reputation: 6883

Like
0Likes
Like

Posted 05 November 2009 - 11:04 AM

Quote:
Original post by Rattenhirn
Quote:
Original post by RandomBystanderAFAIK, they also optimize better than const ints.


What makes you think that?



It depends on the situation.

In general, the compiler will, most of the time, simply inline a const int value as if it were a literal int, however, it is free not to do so. This is because a const int must reside in memory because its address can be taken via the '&' operator (though I wonder if the compiler is free to do away with that if its address is never taken).

An member of an enum, on the other hand, is not a variable, so the compiler can assume that its address can not be taken, and therefore need not reside in memory.

Now, it gets more interesting here because what the compiler can do becomes hardware dependant. Take the ARM arcitecture for instance, which supports, IIRC, literals of up to only 5 bits. If your enum member is within the range of 0 to 31, or -16 to 15, then its may be inserted inline, but if it falls outside this range (possibly even if any single member of the whole enum falls outside this range) then it must be loaded from memory because ARM doesn't as far as I know have any other means to support literals of a larger range.

I'm inclined to agree that an enum allows the compiler more room to make optimization choices (to the extent that the underlying platform allows) versus, say, const ints. In general, the more contextual information the construct supplies, the better the compiler *may* be able to do -- enum supplies more context than const int, if ever so little. That said, the difference this makes in practice, even under the most favorable conditions, is probably negligable. I certainly wouldn't go about changing every 'cont int' in your program to single-member enums [smile]

#10 outRider   Members   -  Reputation: 852

Like
0Likes
Like

Posted 05 November 2009 - 11:15 AM

Quote:
Original post by Ravyne
In general, the compiler will, most of the time, simply inline a const int value as if it were a literal int, however, it is free not to do so. This is because a const int must reside in memory because its address can be taken via the '&' operator (though I wonder if the compiler is free to do away with that if its address is never taken).


Yes it is free to do that and most do since it's fairly trivial, although I've seen some that don't, even though the architecture didn't need literal pools.

#11 Way Walker   Members   -  Reputation: 744

Like
0Likes
Like

Posted 05 November 2009 - 11:32 AM

Quote:
Original post by Rattenhirn
Quote:
Original post by RandomBystander
AFAIK, they also optimize better than const ints.

What makes you think that?


Probably because const is one of the subtle differences between C and C++. In C, const variables default to extern linkage and are not officially constants (so they can't, for example, be used to specify the length of an array in C89) so, in C, there are more contexts where a const variable will have to be treated as a regular variable by the compiler than in C++. Good practice in C isn't always good practice in C++ (and vice versa).

Quote:
Original post by Ravyne
A #define, used as a constant, is roughly equivilent to a C or C++ 'const' variable except... it's address cannot be taken via the & operator


Also true of enum values.

Quote:

And the final kicker is that you can't use the string "PLASMA" anywhere in your code, or it'll be replaced by "3"


The text-replacement is smart enough to understand tokens and strings, so myPLASMA and "PLASMA string" will not be converted to my3 and "3 string". The problem is mostly with errors (You'll get a better error for "int BAR = 0;" if BAR is an enum value than if BAR is a #define constant) and with namespaces (the text-replacement isn't smart enough to understand namespaces).

Quote:

Also, no more multiplying that Matrix by PLASMA, unless you specifically define such an operation taking eMatterState as its parameter.


I don't know a lot about C++ conversions with functions, but 'g++ -Wall' doesn't even issue a warning if you multiply an enum value with a double. So, you can easily get an int from an enum, but you need a cast to go the other way around.


#12 Beyond_Repair   Members   -  Reputation: 116

Like
0Likes
Like

Posted 05 November 2009 - 12:32 PM

Roughly: In ANSI C, constants should be #defines, in C99 constants should be enum members for integers (or more #defines), in C++ constants should be variables with the const keyword or enum members if logical grouping of integers is desired.

Either way, in C++ there's no reason to use #define for constants unless you're trying to keep a header compatible with C. Enums and const variables are semantically safer.

#13 Dbproguy   Members   -  Reputation: 108

Like
0Likes
Like

Posted 09 November 2009 - 02:06 PM

Wow, the answer is very clear. I was expecting everyone to say something about #define being better because my C++ teacher said it was, but I didn't understand why. I think she didn't want to talk about enums at the time or something, but I'll take the word of anyone here at gd.net over her word.

Thanks a lot. I can't believe it is so plain to see, I'll have to think a little harder before posting next time!

#14 MaulingMonkey   Members   -  Reputation: 1556

Like
0Likes
Like

Posted 09 November 2009 - 02:19 PM

Quote:
Original post by Ravyne
Quote:
Original post by Rattenhirn
Quote:
Original post by RandomBystanderAFAIK, they also optimize better than const ints.


What makes you think that?



It depends on the situation.

In general, the compiler will, most of the time, simply inline a const int value as if it were a literal int, however, it is free not to do so. This is because a const int must reside in memory because its address can be taken via the '&' operator (though I wonder if the compiler is free to do away with that if its address is never taken).

With the right settings the compiler will remove entire functions if they're not used. Calling the few bytes you could possibly save when dealing with a dumb or badly configured optimizer is like calling a pea a three course meal. Sure, there's the first third... the second third... and the third third... but it's not nearly as filling as implied [lol]

#15 Washu   Senior Moderators   -  Reputation: 4639

Like
0Likes
Like

Posted 09 November 2009 - 02:35 PM

Quote:
Original post by Ravyne
A #define, used as a constant, is roughly equivilent to a C or C++ 'const' variable except, of course, that it is not typed, that it is inserted through text replacement, and that it's address cannot be taken via the & operator (because a #define never actually exists in memory at runtime).

An enumeration defines a category of related constants, which together constitute a new, distinct type. This means that type safety applies in full, and consequently that enumeration members from other enums and literal values cannot be assigned to the enum type, even when the constant values they represent may be identical to those in the target enumeration.

True in C++, not in C. In C:

enum color { red, green, blue };
color c = 1; // valid in C, not in C++

Quote:

In C, you'd wrap that up in an appropriate typedef, IIRC. Anyhow, now your functions dealing with the state of matter can take a parameter of type "eMatterState", so it can only take one of SOLID, LIQUID, GAS or PLASMA -- There is no way to pass -1 or 17, or even 0, 1, 2, or 3 without going through the eMatterState type. Also, no more multiplying that Matrix by PLASMA, unless you specifically define such an operation taking eMatterState as its parameter.

Not necessarily true, in C and C++ enumeration types are subject to integral promotion rules. Thus:

int i = green * 4; //valid in C and C++

Finally, the value of an enumeration can be specified through the use of explicit conversion from another expression of enumeration or arithmetic type. It will take on the appropriate value within the enumeration, unless it is outside of the bounds of those values defined by the enumeration, in which case the value is unspecified (note: not undefined behavior).




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS