TPDMarchHare TPDMarchHare - 2 months ago 10
C++ Question

Is using chained #defines to declare plain-language integer indices a good idea?

I'm writing a C++ program that uses an integer value to differentiate between a large number of derived class objects. The top-level class stores the integer in the constructor, and has a public method to query it; the idea is to make it so I can quickly tell objects apart when I store them in an array using pointers typed to their top-level class.

I'm using #defines to provide plain-English names for some of the IDs; I want to be able to reference them from (my) memory without the added computational load of using string-based identifiers to find them. I'm reluctant to make a completely static declaration in case I want to add or change identifiers later (for example, some are allocated by block, and I may someday need to increase the block size). I've started using #define to increment the index by one.

I think examples of what I'm doing might be easier:

#define OBJ_FRED 0
#define OBJ_CATHY OBJ_FRED + 1
#define OBJ_JOE OBJ_CATHY + 1
#define OBJ_TRIPLETS_START OBJ_JOE + 1
#define OBJ_NANCY OBJ_TRIPLETS_START + 3
...
// Note that there will potentially be a lot of these.


My question is this: will the preprocessor substitute all occurrences of OBJ_NANCY with "0 + 1 + 1 + 1 + 3" or is it actually evaluating the #defines as it goes along? If it does just substitute the text, should I be concerned that there may be a point at which the preprocessor or compiler will encounter a problem because the #define is too long? I looked around and couldn't find a straight answer to either question. I'm using MS Visual C++ 2015 if it matters.

I'm asking partly out of curiosity, and partly because I'll likely end up with a lot of these and I don't want to have to go back later and change all of them to static values.

Answer

To answer your question as asked, yes the preprocessor will substitute OBJ_NANCY with 0 + 1 + 1 + 1 + 3. That's what the processor does - it expands macros and does text substitution.

However, the processor is only one phase of compilation. Although (strictly speaking) not required to, later phases of compilation will typically evaluate constant expressions e.g. turn 0 + 1 + 1 + 1 + 3 into 6, rather than leaving the evaluation to be done at run time. It is a pretty basic optimisation, and most decent-quality compilers implement it (albeit, possibly as an optional optimisation).

There are implementation-specific limits on the size of code statements (the standards describe lower bounds on those limits). A consequence is that there are limits on the size of a macro expansion. In practice, it is rarely a problem - the limits most implementations support are related to available memory. And, if you are running into those limits, macro expansion is the least of your problems.

As others have said in comments (and another answer), you would be better off using an enumerated type.

You might want to look at your class design. A need for a base class to contain a field that identifies its derived classes is known as a "code smell" - something that is probably unwanted, and for which there are often better alternatives. Even if, sometimes, the smell is tolerable.