#define versus const dataType dataName [= initial value];

I've been teaching myself C (for about a year) and taking a class on C++ (for about 3 weeks). I have been using the #define preprocessor directive to define named constants, such as

#define PI 3.141593

but my C++ teacher introduced the const keyword to define a named constant.

const double PI = 3.141593;

Which is more correct to use, and why? I was introduced to the #define directive while learning C. Is it a C convention while const double PI = 3.141593; is a C++ convention? Thanks for any explanations as to the difference and the pros/cons of either.



Last edited on
C++ tries to replace the preprocessor ( the things beginning with # ) with some other constructs
constants replace the #define s that expand to a single value
A const is better than a #define because it cannot be redefined and is visible only in its scope ( like a variable ) but the result would be quite similar
Thanks Bazzy, that kinda helps-

I know that in the case of #define PI 3.141593 , #define is a preprocessor directive, and once the program is compiled, will go and replace every instance of PI with the value 3.141593. I know that result wise- const double PI = 3.141593; and #define PI 3.141593 are very similar.

What I guess I am asking is which is the preferred method of defining a named constant? As far as I can tell, neither method can be redefined during runtime and both give the same result in the end. Would love some feedback on this, because I am either on my way to greater understanding, or straying off of the path.

const is always better for the simple reason that #defines have global (file) scope whereas consts can be scoped.
Just to add on, the const keyword in C++ can be used in other areas besides defining a constant or replacement for the preprocessor directive #define

E.g
class A {
public :
int getA() const;
int getA();
int getB(const B&);
int getC(const C*);
...
}

class A {
public:
static const A = 10;
...
}
Above require newer C++ compiler.
Thanks all,

This clears things up.
The preprocessor only does text substitutions. This could potentially replace something unexpected, such as part of a longer variable name. Also, as mentioned above, preprocessor directives can propagate across source files easily and often without warning. knowledge that adding a simple header file. Last, but certainly not least, they circumvent the type system.


Header files that provide equates using preprocessor directives are very common. For example:
1
2
3
4
5
6
// this is frowned upon
#define REQUEST_INVALID (0)
#define REQUEST_DO_THIS (1)
//...
#define REQUEST_DO_THAT (100)
#define REQUEST_MAX     (101) // must be last 

Subsequent code may store these request equates as unsigned integers, or some other integral type. Note that inserting a request in the middle of the list forces the developer to renumber the rest of the list. Now consider a better solution:
1
2
3
4
5
6
7
enum Request {
    REQUEST_INVALID = 0, // invalid should always be zero to support initialization to zero and conditional checks
    REQUEST_DO_THIS,
    //...
    REQUEST_DO_THAT,
    REQUEST_MAX // must be last
};

Enumerations can do the counting for you, do not have the drawbacks of preprocessor directives, and, in C++, can be type checked. Subsequent code may now use the Request type rather than an unsigned integer and the compiler will generate an error when an unsupported value is used. One more small caveat is that version control systems can merge additions automatically rather than requiring a manual merge when two developers insert differing requests at the same time.


Again, as mentioned above, consider providing contants in the smallest scope possible. In C++, module-scope constants can be defined in an unnamed namespace:
1
2
3
4
5
6
7
// the.cpp
#include "the.hpp"
//...
namespace {
    const double PI = 3.14159;
}
//... 

Last edited on
Topic archived. No new replies allowed.