Enum vs Const

Introduction

I recently had to review some c++ code written by someone else. The code was well written. It checked for error conditions and did reasonable things if something went wrong. But there was something about it that irked me.

The class CAClass constructor takes a parameter that specifies something of interest to the class, constValue and a pointer to a string. DoSomething() does something relevant based on the parameters passed to the constructor. The class implementation was coded something like this.

There's almost no difference. Any good c++ compiler would compile identical code for both classes.

So what's the difference?

The first class, CAClass, defines a bunch of constant values of interest to itself. CBClass defines the same named constants but it does it as an enum. An enum defines a limited set of valid values and can also be used as a pseudo datatype. Look at the difference in the definitions of the constructors. <PRE lang=c++>CAClass(UINT constValue, LPCTSTR someOtherParameter);
CBClass(constsOfInterestToThisClass constValue, LPCTSTR someOtherParameter);

CAClass can accept any valid UINT value. That's over 4 billion possible values, only 2 of which are of any possible interest to the class. Any valid UINT value outside of 0 or 1 will cause the DoSomething function to raise an exception that other code within your application must handle.

CBClass in contrast will accept only one of the two enum values. Try and pass any invalid constant and the compiler will (should) complain with an error or warning message.

Contrast this <PRE lang=c++>CAClass obj(1000, _T("This is a string"));
obj.DoSomething();

The first example CAClass obj(1000, _T("This is a string")); will compile and throw an exception at runtime when it calls obj.DoSomething(). The second example CBClass obj(1000, _T("This is a string)); will at the very least throw up an error message in your compiler, at compile time. A good implementation will fail to produce an executable file until you've corrected the error and provided a valid value. VC++ flags a warning but produces an executable if you've set error level to 3 and not checked 'warnings as errors'. I always compile my code at error level 4 and 'warnings as errors'.

The CBClass constructor expects a first parameter of type constsOfInterestToThisClass. This may be either bConstantOfInterestToThisClass1 or bConstantOfInterestToThisClass2 or a variable of type constsOfInterestToThisClass. The compiler will let you define a variable of type constsOfInterestToThisClass but will only let you assign values from the enum values you define. <PRE lang=c++>CBClass::constsOfInterestToThisClass var;
var = CBClass::bConstantOfInterestToThisClass1; // OK
var = 47; // Error

Another issue

From reading the foregoing it's tempting to conclude that the final default: case in CBClass::DoSomething() is superfluous. You might even have thought I left it in by mistake. After all, if you've used an enum correctly the default: should never occur. That's true today. But what if you add a new enum constant sometime down the track and forget to add code to the DoSomething() function to handle it? If your switch statement silently ignores enum values it doesn't know about you run the risk of incurring all kinds of unexpected (and difficult to trace) behaviour. Leaving the default: case in place greatly increases your chances of catching such oversights during program development and testing.

Casts

As one or two readers have pointed out it's possible to defeat the whole point of this article by using casts. For example one could code my error example from above thusly:

and the compiler will happily compile your code. Of course it won't run as you expected but if you left the default: code in the switch statement at least you'll get an exception at runtime and hopefully during program testing. What you're doing here of course is saying to the compiler, in effect, 'you know and I know that 47 isn't a valid constant here but I know better than you do so just go ahead and compile it for me'. Once you've asserted your superior knowledge to the compiler all bets are off.

Interestingly, the compiler considers some casts to be so extreme that it still won't compile them without an intermediate step. Ie, cast something to something else, then cast that something else to the final type.

Conclusion

The compiler will do a lot of error checking for you at compile time, if you let it. Using enum's rather than const's helps the compiler find places in your code where you've made incorrect assumptions. The compiler's a lot more thorough than most of us are when it comes to checking datatypes! <!------------------------------- That's it! --------------------------->

Share

About the Author

I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

OK, this is just extended Hungarian notation. This is one of those topics that has been debated in thousands of posts all over the Internet. Personally, I used to adopt Hungarian but no longer do so, given the choice. I find that, certainly with the likes of Java and C#, the recommended conventions by Sun and Microsoft work well. (Microsoft now explicitly reject Hungarian for C#.)

But I can live with either Hungarian or non-Hungarian. It's not as big a deal as conceptual issues such as the one Rob discusses.

Good article for those who do not already make use of enum, some points however:

The code from the first example will not work in a conforming C++ compiler, defining constants like that in the class definition is not allowed. It is only allowed for static const integral types. A constant that is to be used like that would make no sense to be non-static so I'm guessing it was just a "typo".

Another thing is about the values that an enum instance can assume. The default clause in the switch is needed even when no more symbols are added to the enum. Suppose you have the following code:

Here we create an instance of the enum without giving it an initial value and as usual the contents of foo is undefined.

The last and least important point is the fact that explicitly giving values to the enum symbols is only needed if the actual number has some meaning. In the above example it doesn't really matter if bConstantOfInterestToThisClass1 is 0 or some other value.

Dammit you're right. I left out the const specifiers... updating now [edit]I meant static[/edit]

And I'm going to give in to majority opinion and remove the explicit value assignment following the first assignment - though personally I prefer the = value syntax. Why? It gets to be a major pain in the bum to count the values when there are more than 7 or 8.

I think it would be better to use the virtual method and "real" polymorphism instead of bunch of constants...

Additionally if enums are a consecutive numbers it is better to leave the numbering alone and only define the first value, then following ones will increment automagically. That ensures that values are adjusted in case you want to insert a new one in the middle of the existing ones.

LikeItMatters wrote:Any time you have a class vary its behavior via a switch statement, a big alarm should go off in your head.

Yup it should. But each case should be considered on it's merits. In the cases I've shown you have insufficient information to 'automatically' conclude that the solution is wrong, inasmuch as you have no idea what work is performed for each case. That was deliberate. This is a beginners article. I wanted to illustrate the difference between const and enum, not write an article on 'correct' OOP design. Nor did I want to clutter up my examples with irrelevant (for the purpose of the article) implementation details.

George wrote:I think it would be better to use the virtual method and "real" polymorphism instead of bunch of constants...

How do you know?

Imagine the class is a text widget, the options are "left, centerH, right", and "top, centerV, bottom".

Would you create 9 dereived classes for each alignment?
How would you allow a change of alignment?
- Through a factory class that can take any of the thingies?
- implicit converting constuctors?
- type cast operators?

I don't know because it depends on the particular situation as you have rightfully highlighted (hence the smiley was provided for the readers comfort). Perhaps it would be good to have issue raised in the article just to clarify the point...

Thanks for your comments. But I think you've missed the point. The article isn't meant to illustrate 'OOP' design. It's meant to illustrate the difference between const definitions and enum definitions. The title says that, so do my explicit comment that the problem is illustrated with contrived examples.

I do understand your comment about it being better to use virtual functions in some instances. Perhaps I should have shown more detail about the classes I was reviewing that led me to write the article. Be that as it may, I don't agree that it's always better to use multiple classes to eliminate const's. Sometimes it's simpler to use a switch statement.

As just a single example, suppose you have a class that logs errors somewhere. Does it make more sense to encapsulate the error text in the class implementation and specify exactly which error as a constant? Or does it make more sense to derive X classes from a base class, where each derived class encapsulates the text of a single error message? I think most programmers would opt for the first option and not want to clutter up their namespace with many many single purpose classes no matter how 'faithful' it is to the concept of 'OOP'.

Yes, many design principles have exceptions. The thing is to be aware of the principles and understand why you are deviating from them - which means understanding your problem context rather than blindly following the principles.

In the real world (i.e. non-academic, where performance, time-to-market, cost, etc. are a real issue) OOD for the sake of OOD design is unrealistic. Each design concept needs to be evaluated against the issues governing the life of the project or product at hand.

Forcing everything to be pure OO often kills a project. A mix of procedural and OO--following established guidelines for both--produces a smaller, faster product in a shorter timeframe. With any project there are many tradeoffs, not only in design choices.

I don't think codeproject.com is a pure academic forum. It's a learning/reference forum for people working in the software industry. It's important to be able to access proven patterns that take into account the best mix of procedural and object-oriented design, without having to provide a convoluted example that may fit OOD/OOP principles.

I agree. I am a director of software for a small, successful company and projects need to be completed on time or we die. A mixture of OO and common sense procedural techniques allows us to produce solid code in a shorter time-frame than an academic adherence to OO would ever do - especially when you have to deal with using large amounts of legacy code sprinkled in with the new stuff.

I am a big fan of OO but you have to know when to use it as a guide not an absolute rule.

I also like to get compile time warnings and errors rather than run-time and support the author's opinion in this matter.