256

Is it better to use static const variables than #define preprocessor? Or does it maybe depend on the context?

What are advantages/disadvantages for each method?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Patrice Bernassola
  • 14,136
  • 6
  • 46
  • 59
  • 22
    Scott Meyers covers this subject very nicely and thoroughly. His Item#2 in "Effective C++ Third Edition". Two special cases (1) static const is preferred within a class scope for class specific constants; (2) namespace or anonymous scope const is preferred over #define. – Eric May 18 '11 at 01:48
  • 3
    I prefer Enums. Because it is hybrid of both. Doesn't occupy space unless you create a variable of it. If you just want to use as a constant , enum is the best option. It has type safety in C/C++11 std and also a perfect constant. #define is type unsafe , const takes space if compiler can't optimize it. – siddhusingh Jul 01 '13 at 06:54
  • 2
    My decision whether to use `#define` or `static const` (for strings) is driven by **initialization** aspect (it was not mentioned through the answers below): if constant is used within particular compilation unit only, then I go with `static const`, else I use `#define` - avoid static order initialization **fiasco** https://isocpp.org/wiki/faq/ctors#static-init-order – Martin Dvorak Feb 03 '17 at 09:08
  • 1
    If `const`, `constexpr` or `enum` or any variation works in your case, then prefer it to `#define` – Phil1970 Aug 28 '17 at 23:47
  • @MartinDvorak "_avoid static order initialization fiasco_" How is that a problem for constants? – curiousguy Jun 15 '18 at 17:57
  • @curiousguy Because 'static const' constants are potentially variables which need to be initialized and which are referenced from other compilation units like variables, and you don't reliably know when these "constants" (really variables) get initialized. – Johannes Overmann Dec 07 '21 at 08:25

11 Answers11

265

Pros and cons between #defines, consts and (what you have forgot) enums, depending on usage:

  1. enums:

    • only possible for integer values
    • properly scoped / identifier clash issues handled nicely, particularly in C++11 enum classes where the enumerations for enum class X are disambiguated by the scope X::
    • strongly typed, but to a big-enough signed-or-unsigned int size over which you have no control in C++03 (though you can specify a bit field into which they should be packed if the enum is a member of struct/class/union), while C++11 defaults to int but can be explicitly set by the programmer
    • can't take the address - there isn't one as the enumeration values are effectively substituted inline at the points of usage
    • stronger usage restraints (e.g., incrementing - template <typename T> void f(T t) { cout << ++t; } won't compile, though you can wrap an enum into a class with implicit constructor, casting operator and user-defined operators)
    • each constant's type taken from the enclosing enum, so template <typename T> void f(T) get a distinct instantiation when passed the same numeric value from different enums, all of which are distinct from any actual f(int) instantiation. Each function's object code could be identical (ignoring address offsets), but I wouldn't expect a compiler/linker to eliminate the unnecessary copies, though you could check your compiler/linker if you care.
    • even with typeof/decltype, can't expect numeric_limits to provide useful insight into the set of meaningful values and combinations (indeed, "legal" combinations aren't even notated in the source code, consider enum { A = 1, B = 2 } - is A|B "legal" from a program logic perspective?)
    • the enum's typename may appear in various places in RTTI, compiler messages, etc. - possibly useful, possibly obfuscation
    • you can't use an enumeration without the translation unit actually seeing the value, which means enums in library APIs need the values exposed in the header, and make and other timestamp-based recompilation tools will trigger client recompilation when they're changed (bad!)

  1. consts:

    • properly scoped / identifier clash issues handled nicely
    • strong, single, user-specified type
      • you might try to "type" a #define ala #define S std::string("abc"), but the constant avoids repeated construction of distinct temporaries at each point of use
    • One Definition Rule complications
    • can take address, create const references to them etc.
    • most similar to a non-const value, which minimises work and impact if switching between the two
    • value can be placed inside the implementation file, allowing a localised recompile and just client links to pick up the change

  1. #defines:

    • "global" scope / more prone to conflicting usages, which can produce hard-to-resolve compilation issues and unexpected run-time results rather than sane error messages; mitigating this requires:
      • long, obscure and/or centrally coordinated identifiers, and access to them can't benefit from implicitly matching used/current/Koenig-looked-up namespace, namespace aliases, etc.
      • while the trumping best-practice allows template parameter identifiers to be single-character uppercase letters (possibly followed by a number), other use of identifiers without lowercase letters is conventionally reserved for and expected of preprocessor defines (outside the OS and C/C++ library headers). This is important for enterprise scale preprocessor usage to remain manageable. Third-party libraries can be expected to comply. Observing this implies migration of existing consts or enums to/from defines involves a change in capitalisation, and hence requires edits to client source code rather than a "simple" recompile. (Personally, I capitalise the first letter of enumerations but not consts, so I'd be hit migrating between those two too - maybe time to rethink that.)
    • more compile-time operations possible: string literal concatenation, stringification (taking size thereof), concatenation into identifiers
      • downside is that given #define X "x" and some client usage ala "pre" X "post", if you want or need to make X a runtime-changeable variable rather than a constant you force edits to client code (rather than just recompilation), whereas that transition is easier from a const char* or const std::string given they already force the user to incorporate concatenation operations (e.g. "pre" + X + "post" for string)
    • can't use sizeof directly on a defined numeric literal
    • untyped (GCC doesn't warn if compared to unsigned)
    • some compiler/linker/debugger chains may not present the identifier, so you'll be reduced to looking at "magic numbers" (strings, whatever...)
    • can't take the address
    • the substituted value need not be legal (or discrete) in the context where the #define is created, as it's evaluated at each point of use, so you can reference not-yet-declared objects, depend on "implementation" that needn't be pre-included, create "constants" such as { 1, 2 } that can be used to initialise arrays, or #define MICROSECONDS *1E-6 etc. (definitely not recommending this!)
    • some special things like __FILE__ and __LINE__ can be incorporated into the macro substitution
    • you can test for existence and value in #if statements for conditionally including code (more powerful than a post-preprocessing "if" as the code need not be compilable if not selected by the preprocessor), use #undef-ine, redefine, etc.
    • substituted text has to be exposed:
      • in the translation unit it's used by, which means macros in libraries for client use must be in the header, so make and other timestamp-based recompilation tools will trigger client recompilation when they're changed (bad!)
      • or on the command line, where even more care is needed to make sure client code is recompiled (e.g. the Makefile or script supplying the definition should be listed as a dependency)

My personal opinion:

As a general rule, I use consts and consider them the most professional option for general usage (though the others have a simplicity appealing to this old lazy programmer).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 1
    Awesome answer. One small nit : i sometimes use local enums that are not in headers at all just for clarity of the code, like in small state machines and such. So they don't have to be in headers, at all times. – kert Jun 09 '14 at 15:46
  • 1
    The pro and cons are mixed up, I would be very like to see a comparison table. – Unknown123 Apr 25 '19 at 03:49
  • 5
    @Unknown123: feel free to post one - I don't mind if you rip off any points you feel worthy from here. Cheers – Tony Delroy Apr 25 '19 at 19:57
156

Personally, I loathe the preprocessor, so I'd always go with const.

The main advantage to a #define is that it requires no memory to store in your program, as it is really just replacing some text with a literal value. It also has the advantage that it has no type, so it can be used for any integer value without generating warnings.

Advantages of "const"s are that they can be scoped, and they can be used in situations where a pointer to an object needs to be passed.

I don't know exactly what you are getting at with the "static" part though. If you are declaring globally, I'd put it in an anonymous namespace instead of using static. For example

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • That's what I want to know. In my company, the strategy is to put all string constant as #define in a separate file. I was thinking it can be better to use const vars instead. Thanks – Patrice Bernassola Oct 28 '09 at 13:57
  • 12
    *String* constants specifically are one of those that might benefit from being `#define`d, at least if they can be used as "building blocks" for bigger string constants. See my reply for an example. – AnT stands with Russia Oct 28 '09 at 14:10
  • A case I had not considered. Thanks for your time. – Patrice Bernassola Oct 28 '09 at 14:17
  • 66
    The `#define` advantage of not using any memory is inaccurate. The "60" at the example has to be stored somewhere, regardless if it's `static const` or `#define`. In fact, I've seen compilers where using #define caused massive (read-only) memory consumption, and static const used no un-needed memory. – Gilad Naor Jul 27 '10 at 08:10
  • 5
    A #define is like if you had typed it in, so its definitely not coming from memory. – the Reverend Sep 24 '11 at 19:52
  • 1
    "The main advantage to a #define is that it requires no memory to store in your program" Isn't the compiler smart enough to do the same thing with constants? – endolith Feb 21 '13 at 22:40
  • 1
    @endolith It'd have to be a pretty dang smart compiler. For instance, constants can be passed into `const &` parameters, which requires that they have an address associated with them. – T.E.D. Feb 21 '13 at 23:15
  • 31
    @theReverend Are literal values somehow exempt from consuming machine resources? No, they just might use them different ways, maybe it won't appear on the stack or heap, but at some point the program is loaded into memory along with all the values compiled into it. – Sqeaky Jul 03 '13 at 01:07
  • 17
    @gilad-naor, You are right in general but small integers like 60 may actually sometimes be a sort of partial exception. Some instruction sets have the ability to encode integers or a subset of integers directly in the instruction stream. For example MIPs add immediate (http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html). In this sort of case a #defined integer truly could be said to use no space since in the compiled binary it occupies a few spare bits in instructions which had to exist anyway. – ahcox Jun 25 '15 at 15:23
  • 1
    When using `static`, you get to use them in delegating constructors (https://en.wikipedia.org/wiki/C%2B%2B11#Object_construction_improvement), since it is available at the instantiation time. – Mak Nov 28 '15 at 08:12
50

If this is a C++ question and it mentions #define as an alternative, then it is about "global" (i.e. file-scope) constants, not about class members. When it comes to such constants in C++ static const is redundant. In C++ const have internal linkage by default and there's no point in declaring them static. So it is really about const vs. #define.

And, finally, in C++ const is preferable. At least because such constants are typed and scoped. There are simply no reasons to prefer #define over const, aside from few exceptions.

String constants, BTW, are one example of such an exception. With #defined string constants one can use compile-time concatenation feature of C/C++ compilers, as in

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

P.S. Again, just in case, when someone mentions static const as an alternative to #define, it usually means that they are talking about C, not about C++. I wonder whether this question is tagged properly...

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
10

#define can lead to unexpected results:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Outputs an incorrect result:

y is 505
z is 510

However, if you replace this with constants:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

It outputs the correct result:

y is 505
z is 1010

This is because #define simply replaces the text. Because doing this can seriously mess up order of operations, I would recommend using a constant variable instead.

5

Using a static const is like using any other const variables in your code. This means you can trace wherever the information comes from, as opposed to a #define that will simply be replaced in the code in the pre-compilation process.

You might want to take a look at the C++ FAQ Lite for this question:

Newbie Questions / Answers

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Percutio
  • 979
  • 7
  • 8
  • The link is half-broken (page anchor "faq-29.7" does not exist). Which one does it refer to? Is it *"[Why would I use a const variable / const identifier as opposed to #define?](http://isocpp.org/wiki/faq/newbie#const-vs-define)"*? – Peter Mortensen Aug 19 '23 at 17:57
5
  • A static const is typed (it has a type) and can be checked by the compiler for validity, redefinition, etc.
  • a #define can be redefined, undefined, whatever.

Usually you should prefer static consts. It doesn't have any disadvantage. The preprocessor should mainly be used for conditional compilation (and sometimes for really dirty tricks maybe).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
RED SOFT ADAIR
  • 12,032
  • 10
  • 54
  • 92
3

Defining constants by using preprocessor directive #define is not recommended to apply not only in C++, but also in C. These constants will not have the type. Even in C, it was proposed to use const for constants.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • [An alternative](https://stackoverflow.com/questions/1712592/variably-modified-array-at-file-scope/40961072#40961072) to #define (in C). – Peter Mortensen Aug 31 '23 at 13:35
3

As a rather old and rusty C programmer who never quite made it fully to C++ because other things came along and is now hacking along getting to grips with Arduino my view is simple.

#define is a compiler pre processor directive and should be used as such, for conditional compilation etc.. E.g. where low level code needs to define some possible alternative data structures for portability to specif hardware. It can produce inconsistent results depending on the order your modules are compiled and linked. If you need something to be global in scope then define it properly as such.

const and (static const) should always be used to name static values or strings. They are typed and safe and the debugger can work fully with them.

enums have always confused me, so I have managed to avoid them.

Martin How
  • 31
  • 1
  • 2
    Hi Martin, could you clarify why enums confuse you? – Nick Gallimore Aug 28 '20 at 17:17
  • 1
    I think that if i was clear about that i would be less confused/wary of using them. Thay are complex data objects. C++ is strongly typed and usually its obvious what you are working with and how to handle it safely. It not immediately obvious what the type of the result of querying an enum. In addition the declarations such as: /enum Foo { a, b, c = 10, d, e = 1, f, g = f + c }; //a = 0, b = 1, c = 10, d = 11, e = 1, f = 2, g = 12 where values are assigned implicitly seem to leave plenty of scope for getting it wrong. – Martin How Aug 31 '20 at 12:08
  • It may not look that way, but Arduino is more C++ than C. E.g., 'const' variables can be used for allocating arrays at compile time, [unlike C](https://stackoverflow.com/questions/1712592/variably-modified-array-at-file-scope/40961072#40961072) (in C, this may result in something like *"error: variably modified 'portDefinitions' at file scope"*). Also, classes, incl. inheritance and pure virtual functions, works just fine in an Arduino program (e.g., lends itself well to be used to hide the details of hardware or hardware abstractions). – Peter Mortensen Aug 19 '23 at 18:33
2

Always prefer to use the language features over some additional tools like the preprocessor.

ES.31: Don't use macros for constants or "functions"

Macros are a major source of bugs. Macros don't obey the usual scope and type rules. Macros don't obey the usual rules for argument passing. Macros ensure that the human reader sees something different from what the compiler sees. Macros complicate tool building.

From C++ Core Guidelines

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hitokage
  • 733
  • 8
  • 19
1

Please see here: static const vs define

Usually a const declaration (notice it doesn't need to be static) is the way to go.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ennuikiller
  • 46,381
  • 14
  • 112
  • 137
0

If you are defining a constant to be shared among all the instances of the class, use static const. If the constant is specific to each instance, just use const (but note that all constructors of the class must initialize this const member variable in the initialization list).