🔒 Locked
Nitpicky question...
Advertisement
Original Post
A buddy of mine got one of those little quizes that potential employers sometimes give, and it had a question on it that I'm curious about. It was wanting to know the pros and cons of various ways of declaring or using constants. The ways given were... #define BLAH ... const int BLAH enum { BLAH = 0, ... }; typedef enum {BLAH, ... } BLAHTYPE; I was thinking that #define has some benefit, because it doesn't actually allocate any memory at runtime. But then I started thinking about the const int approach, and I'm not sure that that one actually does either. Wouldn't that be dealt with at compile time as well? And then the typedef approach, that probably has some casting issues or something, but I'm not really sure at this point. Any one have thoughts or opinions? (Strictly curiosity, by the way... no homework or anything).
Prefer not to use #defines. They're not type-safe, don't respect scoping rules and can lead to serious logic errors when the macro goes awry. By default, use a const.
Enums can be useful to isolate a specfic type of constant, eg. days of the week, weapon types, etc. Use these when you need to be sure a specific type is passed, instead of just an integral constant. They also have uses unnamed in classes using certain template techniques.
typedef enum {} enum_type: I have no idea why this would be useful.
Enums can be useful to isolate a specfic type of constant, eg. days of the week, weapon types, etc. Use these when you need to be sure a specific type is passed, instead of just an integral constant. They also have uses unnamed in classes using certain template techniques.
typedef enum {} enum_type: I have no idea why this would be useful.
A bit more info on const int BLAH vs a #define from the C++ FAQ Lite
Quote:
const identifiers are often better than #define because:
* they obey the language's scoping rules
* you can see them in the debugger
* you can take their address if you need to
* you can pass them by const-reference if you need to
* they don't create new "keywords" in your program.
- #define
- pros:
- none
- cons:
- names do not respect scoping rules
- no type safety
- no symbol table entries
- do not prevent user passing invalid constants
- pros:
- const int
- pros:
- names respect scoping rules
- type safe
- have symbol table entries
- cons:
- all constants have same type
- do not prevent user passing invalid constants
- on older compilers cannot be used to provide a constant integral class member for use in declaring the size of a member array
- pros:
- enum {...};
- pros:
- names respect scoping rules
- type safe
- have symbol table entries
- different enums have different types
- cons:
- do not prevent user passing invalid constants
- enum type cannot be referred to and therefore enum values cannot be stored in a type safe manner
- pros:
- typedef enum {...}...;
- pros:
- names respect scoping rules
- type safe
- have symbol table entries
- different enums have different types
- enum type can be referred to and therefore stored
- cons:
- do not prevent user passing invalid constants
- pros:
Then there's my preferred method, which is to declare a new class with static instances representing the constants. It has more overhead to generate and cannot be used to declare the size of a member array, but has no other cons that I am aware of:
class MyConstant{ public: static MyConstant const Value1; static MyConstant const Value2; static MyConstant const Value3; bool operator==(MyConstant const constant) const { return id_ == constant.id_; } bool operator!=(MyConstant const constant) const { return !operator==(constant); } MyConstant operator&(MyConstant const constant) const { return MyConstant(id_ & constant.id_); } MyConstant operator|(MyConstant const constant) const { return MyConstant(id_ | constant.id_); } // I prefer to have a named constant for "none" and no implicit // conversion to bool operator bool() const { return id_ != 0; } private: MyConstant(unsigned int id) : id_(id) { } MyConstant & operator=(MyConstant const & constant); unsigned int const id_;};MyConstant const MyConstant::Value1 = MyConstant(0x1);MyConstant const MyConstant::Value2 = MyConstant(0x2);MyConstant const MyConstant::Value3 = MyConstant(0x4);The supported operations can be changed as required (remove logical ops, add relational ops etc.). The static constants can also be changed to static functions which return references to local static objects if non-local static initialisation order is an issue.
Enigma
Quote:
Original post by Enigma
*** Source Snippet Removed ***
The supported operations can be changed as required (remove logical ops, add relational ops etc.). The static constants can also be changed to static functions which return references to local static objects if non-local static initialisation order is an issue.
Enigma
I do something similar.
class Justification{public: enum { LEFT, CENTER, RIGHT, STRETCH }; int asInt(void) const { return justification; } static Justification left(void) { return LEFT; } static Justification center(void) { return CENTER; } static Justification right(void) { return RIGHT; } static Justification stretch(void) { return STRETCH; }private: int justification; Justification(int i) : justification(i) { }};You could typedef the enum if you wanted, but the only reason the enums are there are for convenience for users using switch statements.
Since I am using static functions, you can't do a switch like:
switch (justification.asInt()){ case Justification::left(): break; case Justification::center(): break; // etc..}because Justification::left() is not an ICE (actually, this bugs me, because just like std::numeric_limits
switch (justification.asInt()){ case 0: break; case 1: break; // etc..}but using the integral constants loses context information for the casual reader. My preferred method is:
switch (justification.asInt()){ case Justification::LEFT: break; case Justification::CENTER: break; // etc..}I'm certainly not tied to this implementation though, I just never bothered coming up with better.
Wow, Enigma, that was a very helpful answer. Thanks! You too mfawcett.
Quote:
Original post by Enigma
- #define
- pros:
- none
There is one pro: In C, const means "read only" so #define is the way to get a true constant. Sure, "none" is more dramatic, but one should give credit where credit is due.
Quote:
- cons:
- names do not respect scoping rules
This is the big one.
Quote:
- no type safety
In C, this is only an issue with variadic functions, but what isn't an issue with variadic functions? At least, I haven't seen an example where type safety was an issue (I would love an example to prove me wrong). I'm not entirely sure this is even a big problem in C++, but my knowledge there is weaker.
Quote:
- no symbol table entries
I've seen #define names in debuggers before, so unless you need to take the address this isn't really an issue (and I think C99 even allows a way around that).
Quote:
- do not prevent user passing invalid constants
True, but listed for every entry.
I'm not supporting #define, it just rubs me the wrong way when someone complains about #define's typesafety, especially when talking about constants (there's a better case for typesafety issues in macros). It's as much a knee-jerk reaction as some people's reaction to #define/goto/etc.
Take what I say with a grain of salt, aside from RAII I dislike C++.
The problem comes in the general compatability between certain integers and pointers. A non-typesafe integer can be mistakenly assigned to a pointer, causing bogus memory to be accessed, leading to a crash; followed by the tedious process of tracking down the run-time bug. Compile time bugs are far more straightforward in their fixing.
That said, sometimes having the #define be not-type safe is an advantage.
Not often though. Frankly, I don't find much use for constants anymore. Far more often the ability to specify the constants programatically or configure them via file/server at initialization is of greater benefit.
That said, sometimes having the #define be not-type safe is an advantage.
Not often though. Frankly, I don't find much use for constants anymore. Far more often the ability to specify the constants programatically or configure them via file/server at initialization is of greater benefit.
#define has one pro - you can do something like the following:
#define some_value (get_some_value())
This is done somewhere in the standard Linux header files (I think it's for thread safety); but I can't remember where.
Edit: I remember, it's errno. And it's defined more like:
#define errno (*get_errno_addr())
#define some_value (get_some_value())
This is done somewhere in the standard Linux header files (I think it's for thread safety); but I can't remember where.
Edit: I remember, it's errno. And it's defined more like:
#define errno (*get_errno_addr())
Quote:
Original post by Enigma
- #define
- pros:
- none
I disagree. The pro to #define is that it is available to preprocessor conditionals.
const int VERSION = 5;#if VERSION == 5 //I warn you: !defined VERSIONconst char* Out = "Welcome";#elseconst char* Out = "Hello";#endifOut is "Hello".
#define VERSION 5#if VERSION == 5const char* Out = "Welcome";#elseconst char* Out = "Hello";#endifOut is "Welcome".
However, that is the only "pro" I see in C++. I would list the flexability to do ZQJ's bit of voodoo as both a pro and a con...
[Edited by - Erzengeldeslichtes on October 21, 2005 6:39:49 AM]
Quote:
Original post by LessBread
Preprocessor abuse...
To some, but beauty is in the eyes of the beholder - Boost.Preprocessor [grin][lol]
Quote:
Original post by Way Walker Quote:
- do not prevent user passing invalid constants
True, but listed for every entry.
I'm not supporting #define, it just rubs me the wrong way when someone complains about #define's typesafety, especially when talking about constants (there's a better case for typesafety issues in macros). It's as much a knee-jerk reaction as some people's reaction to #define/goto/etc.
Take what I say with a grain of salt, aside from RAII I dislike C++.
Not true for every entry. The examples Enigma and I posted both stop the user from passing invalid constants. Although I'm sure they won't be to your liking since you dislike C++.
typedef enum {BLAH, ... } BLAHTYPE;
can be better done as
enum BLAHTYPE {BLAH, ... };
at least in C++.
enums are also guaranteed to not allocate memory at runtime, which is not the case with consts. This is especially relevant for static consts in templated classes, because if they end up /not/ being no-storage-allocated-compile-time-constants, every time you instatiate the class you use up some memory. It's also a good way to be sure that your array size constants or integral template parameters are actually compile-time constant.
can be better done as
enum BLAHTYPE {BLAH, ... };
at least in C++.
Quote:
Original post by smitty1276
I was thinking that #define has some benefit, because it doesn't actually allocate any memory at runtime. But then I started thinking about the const int approach, and I'm not sure that that one actually does either. Wouldn't that be dealt with at compile time as well?
enums are also guaranteed to not allocate memory at runtime, which is not the case with consts. This is especially relevant for static consts in templated classes, because if they end up /not/ being no-storage-allocated-compile-time-constants, every time you instatiate the class you use up some memory. It's also a good way to be sure that your array size constants or integral template parameters are actually compile-time constant.
Quote:
Original post by me22
This is especially relevant for static consts in templated classes, because if they end up /not/ being no-storage-allocated-compile-time-constants, every time you instatiate the class you use up some memory.
Surely if they're static they only use up the memory once (and are initialised at startup)?
Quote:
Original post by snk_kid Quote:
Original post by Way Walker
There is one pro: In C, const means "read only" so #define is the way to get a true constant.
Erm const has been C since C89.
I know that. However, const in C means something different than it does in C++. The canonical example is the size of an array:
const int size = 10;int main(void) { int array[size] = {0}; return 0;}This produces an array in C++, a VLA in C99, and is illegal in C89. const means "constant" in C++ and "read only" in C.
Quote:
Original post by mfawcett
Not true for every entry. The examples Enigma and I posted both stop the user from passing invalid constants. Although I'm sure they won't be to your liking since you dislike C++.
Yes, of course. I was refering to the four options given by the OP and in Enigma's list. Your preferred method was basically a footnote in Enigma's post. I'm sure this was because it's not directly related to smitty1276's question.
For what it's worth, the best word I can think of to describe your solution is "intriguing". Using classes to encapsulate constants is an interesting idea. I have two objections:
1) It's not perfect. For integer constants, it doesn't really buy you anything more than a typedef'd enum. A compiler should warn you when using a constant that isn't valid for the enum, and you should really be using the named constants anyway. So, it looks to me, in its current form, to be a lot of work for something not much better than a typedef'd enum. Its major benefit is that you can now have non-integer enums. I wonder if it can be "perfected" (obviously you can't prevent a determined hacker, but see my second objection). I remember seeing something on trying to create a max() template that behaved, in all respects, better than "#define MAX(a,b) (((a)>(b))?(a):(b))". It took a lot, but I think it ended up with a solution. Of course, the only real difference is that the template wouldn't evaluate its arguments twice.
2) I cut my teeth on Smalltalk where there's a lot more trust/respect for your fellow programmers. All messages are public. C++'s private/protected is approximated by putting messages in a category designated private. If another programmer decides to use a message from this category, he's been warned and accepts the consequences. It's assumed that he has weighed the consequences and decided it's worth it. Similarly, I've heard some Java programmers argue that private should never be used and everything that would have been private should be protected. This allows access to those internals should the need arise, but still hides them from a casual user.
Quote:
Original post by Enigma
- #define
- pros:
- none
- cons:
- names do not respect scoping rules
- no type safety
- no symbol table entries
- do not prevent user passing invalid constants
That's really untrue. There are many benefits of using #defines. For one, they are useable as preprocessor constants, unlike const integer types and enums constants. This makes them able to be used in preprocessor expressions, and are particularly useful for any preprocessor metaprogramming.
As well, #defines allow you to give names to floating point constants and still have them be preprocessor and compile-time constants. const floats and const doubles are not useable at compile-time nor at preprocessor time, whereas #defines are on both accounts since they represent literals.
#defines can also be just as typesafe as literals, since that's what they usually directly represent anyway. Using PI in an expression as typesafe as using 3.14159... as a literal in an expression. The #define just gives it a name.
Still, you should prefer using const integral types instead wherever you can, since a compile-time integer constant is mostly what you need and you get proper scoping and semantics of variables. #defines, however, do still have their uses. Saying they have no "Pros" is just wrong.
Quote:
Original post by Way Walker
1) It's not perfect. For integer constants, it doesn't really buy you anything more than a typedef'd enum. A compiler should warn you when using a constant that isn't valid for the enum, and you should really be using the named constants anyway. So, it looks to me, in its current form, to be a lot of work for something not much better than a typedef'd enum. Its major benefit is that you can now have non-integer enums. I wonder if it can be "perfected" (obviously you can't prevent a determined hacker, but see my second objection). I remember seeing something on trying to create a max() template that behaved, in all respects, better than "#define MAX(a,b) (((a)>(b))?(a):(b))". It took a lot, but I think it ended up with a solution. Of course, the only real difference is that the template wouldn't evaluate its arguments twice.
But you can't use the integer constants except for read access, so it will always be one of the valid integer constants. In the typedef'd enum situation, you have:
typedef enum { LEFT, CENTER, RIGHT } Justification;void justify(Justification j){ switch (j) { case LEFT: // stuff. break; case CENTER: // stuff. break; case RIGHT: // stuff. break; default: // This is possible with typedef'd enums }}int main(void){ Justification j; // Valid with typdef'd enums justify(j); // What will this do?}With the method I posted, it is not possible to generate invalid Justifications.
class Justification{public: enum { LEFT, CENTER, RIGHT }; int asInt(void) const { return justification; } static Justification left(void) { return LEFT; } static Justification center(void) { return CENTER; } static Justification right(void) { return RIGHT; }private: int justification; Justification(int i) : justification(i) { }};void justify(Justification j){ switch (j.asInt()) { case Justification::LEFT: // stuff. break; case Justification::CENTER: // stuff. break; case Justification::RIGHT: // stuff. break; default: // This is impossible with our implementation }}int main(void){ Justification j; // Impossible! Compile-time error justify(Justification::left());}Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement