Many people have written about the problems of using macros in C. In C++, constants and templates should pretty much eliminate the need to use macros. Unless you are seriously pressed for speed, there should be no reason to use macros in C++.
In straight C, the keyword "const" doesn't produce constants that can be used to initialize array sizes.
/* Will not work in ANSI C, but will work in C++ */
int const ARRAY_SIZE = 100;
int array[ ARRAY_SIZE ];
The most common way I've seen people deal with this is by using macros:
#define ARRAY_SIZE 100
int array[ ARRAY_SIZE ];
This always works, but it has a few problems. Lets make the statement a little more complex.
#define ARRAY_SIZE 100 + 10
int array[ ARRAY_SIZE * 2 ];
How big is the array going to be? If you said 220 elements, you would be wrong--the correct answer is 120. This is due to the nature of macros. They are pre-processor substitutions and are not evaluated. What really happens is the following code is generated:
int array[ 100 + 10 * 2 ];
Now remember your operator precedence--10*2 will happen before adding 100, so the size of the array is 100 + ( 10 * 2 ) = 120.
Most programmers know to use parenthesis around everything defined in a macro.
#define ARRAY_SIZE ( 100 + 10 )
int array[ ARRAY_SIZE * 2 ];
This will produce the 220 elements desired. However, I learned a trick at my previous contract job that can avoid this whole scenario. Instead of using macro, use an entry from an enumeration. ANSI C says that a value in an enum is always treated as an constant int. enum values can be assigned to integer values and enums can be anonymous. So this syntax is valid:
enum { ARRAY_SIZE = 100 + 10 };
int array[ ARRAY_SIZE * 2 ];
Note we don't need to use parenthesis (although it wouldn't hurt) like we need to with a macro. enums are not pre-processor substitution and are evaluated at compile time. They are, in many respects, a C version of C++ const.
I use the anonymous enum constant instead of macros when ever I can. They always work for array sizes and work for most constants. However, there are some places they may not do the job. enums are always of type "int". If you need a floating-point constant, you'll be stuck with either a macro or a "const". And the int size can be different on different machines. This can really be an issue if you are making masks. For example:
enum { MASK = 0xFFFFFFFFL };
This will create problems on a 16-bit compiler where the int size is 2-bytes. For such cases, you can still use "const".
double const PI = 3.14159265358979323846;
uint32 const MASK = 0xFFFFFFFF;
The drawback is that in standard C, const values are required to take up storage space. So in this example:
double const PI = 3.14159265358979323846;
double const _2_PI = PI * 2;
Even if PI is never referenced in the source code again, it still has storage allocated. Where as:
#define PI (3.14159265358979323846)
#define _2_PI (PI * 2)
Will never result in unused storage. In most cases, we're talking about a few bytes. If you are using a constant value for something other then array initialization, having storage allocated can actually produce smaller code. Rather then loading to a constant value every time the value is used, the code can load from a location in memory. Again, we're talking savings in the bytes. So unless you are an embedded programmer with a really tight ROM/RAM budget, it's hardly worth worrying about.
In C++, the rules for const change. Storage for a "const" object isn't necessarily allocated. So in our previous example with PI and _2_PI, the compiler is free to discard PI if it isn't used anywhere else. In addition, const values can be used to initialize arrays.
// Valid C++ syntax
int const ARRAY_SIZE = 100;
int array[ ARRAY_SIZE ];
The compiler is free to discard storage for ARRAY_SIZE. In most cases, C++ const will do anything we had to do with enum values. And in most cases, the value will be used directly rather then loading from a memory location--although this may depend on your compiler.
uint32 const MASK = 0xAAAA5555;
int const ARRAY_SIZE = 0x100000;
int array[ ARRAY_SIZE ];
// ...
for ( int index = 0; index < SOME_VALUE; ++index )
{
array[ index ] &= MASK;
}
The above loop might need to happen in some time-critical location. Assuming the compiler has no optimization, it would be preferable that MASK were evaluated as an intermediate and not stored at some memory location. Otherwise time is wasted loading the value from the memory location. However, even the most basic optimization will take care of this problem.