96

I want to define a constant in C++ to be visible in several source files. I can imagine the following ways to define it in a header file:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Some function returing the value (e.g. int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; and in one source file const int GLOBAL_CONST_VAR = 0xFF;

Option (1) - is definitely not the option you would like to use

Option (2) - defining instance of the variable in each object file using the header file

Option (3) - IMO is over killing in most cases

Option (4) - in many cases maybe not good since enum has no concrete type (C++0X will add possibility to define the type)

So in most cases I need to choose between (5) and (6). My questions:

  1. What do you prefer (5) or (6)?
  2. Why (5) is ok, while (2) is not?
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
dimba
  • 26,717
  • 34
  • 141
  • 196
  • 1
    5 versus 2: "const" implies internal linkage. When you include this version-5 header into multiple translation units you won't be violating the "one definition rule". Also, const allows the compiler to do "constant folding" whereas the value of the non-const variable may change. Option 6 is wrong. You need "extern" in the cpp file as well to force external linkage, otherwise you'll get linker errors. Option 6 has the advantage of hiding the value. But it also makes constant folding impossible. – sellibitze Feb 16 '10 at 13:01

10 Answers10

83

Definitely go with option 5 - it's type safe and allows compiler to optimize (don't take address of that variable :) Also if it's in a header - stick it into a namespace to avoid polluting the global scope:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;
Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • 6
    I get the redefinition error when try to include `header.hpp` in several source files. – LRDPRDX Jul 28 '17 at 03:44
  • 1
    Not sure why this still gets upvoted - it's been almost ten years, but nowdays we have `constexpr` and typed enumerations for things like that. – Nikolai Fetissov Jun 07 '19 at 09:24
38

(5) says exactly what you want to say. Plus it lets the compiler optimize it away most of the time. (6) on the other hand won't let the compiler ever optimize it away because the compiler doesn't know if you'll change it eventually or not.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • 1
    OTOH, 5 is technically illegal as a violation of the ODR. Most compilers will ignore it, however. – Joel Feb 15 '10 at 21:07
  • Eh, I prefer to think of it as not having defined anything at all, I just told the compiler to give a pretty name to a number. For all intents and purposes that's what (5) is, which means no overhead at runtime. – Blindy Feb 15 '10 at 21:18
  • 3
    Is (5) a violation of the ODR? If it is, then (6) is preferable. Why does the compiler _"not know if you'll change it"_ in the case of (6)? `extern const int ...` and `const int ...` are both constant are they not? – D.Shawley Feb 15 '10 at 21:41
  • 4
    AFAIK, between 5) and 6), only 6) is allowed when the type of the constant is not int-based. – Klaim Feb 15 '10 at 21:45
  • Oh oops, I missed the const in (6). – Blindy Feb 15 '10 at 21:52
  • No ODR violation if you add static: `static const int Global = 0xFF;`. This way each compilation unit get "private copy" of Global which is optimized away. – sbk Feb 15 '10 at 23:09
  • 12
    There is no ODR violation, constant objects are static by default. – avakar Feb 15 '10 at 23:12
28

(5) is "better" than (6) because it defines GLOBAL_CONST_VAR as an Integral Constant Expression (ICE) in all translation units. For example, you will be able to use it as array size and as case label in all translation units. In case of (6) GLOBAL_CONST_VAR will be an ICE only in that translation unit where it is defined and only after the point of definition. In other translation units it won't work as ICE.

However, keep in mind that (5) gives GLOBAL_CONST_VAR internal linkage, meaning that the "address identity" of GLOBAL_CONST_VAR will be different in each translation unit, i.e. the &GLOBAL_CONST_VAR will give you a different pointer value in each translation unit. In most usage cases this doesn't matter, but if you'll need a constant object that has consistent global "address identity", then you'd have to go with (6), sacrificing the ICE-ness of the constant in the process.

Also, when the ICE-ness of the constant is not an issue (not an integral type) and the size of the type grows larger (not a scalar type), then (6) usually becomes a better approach than (5).

(2) is not OK because the GLOBAL_CONST_VAR in (2) has external linkage by default. If you put it in header file, you'll usually end up with multiple definitions of GLOBAL_CONST_VAR, which is an error. const objects in C++ have internal linkage by default, which is why (5) works (and which is why, as I said above, you get a separate, independent GLOBAL_CONST_VAR in each translation unit).


Starting from C++17 you have an option of declaring

inline extern const int GLOBAL_CONST_VAR = 0xFF;

in a header file. This gives you an ICE in all translation units (just like method (5)) at the same time maintaining global address identity of GLOBAL_CONST_VAR - in all translation units it will have the same address.

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

If you use C++11 or later, try using compile-time constants:

constexpr int GLOBAL_CONST_VAR{ 0xff };
xninja
  • 249
  • 4
  • 7
7

C++17 inline variables

This awesome C++17 feature allow us to:

  • conveniently use just a single memory address for each constant
  • store it as a constexpr: How to declare constexpr extern?
  • do it in a single line from one header

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Compile and run:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream.

See also: How do inline variables work?

C++ standard on inline variables

The C++ standard guarantees that the addresses will be the same. C++17 N4659 standard draft 10.1.6 "The inline specifier":

6 An inline function or variable with external linkage shall have the same address in all translation units.

cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static is not given, then it has external linkage.

Inline variable implementation

We can observe how it is implemented with:

nm main.o notmain.o

which contains:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

and man nm says about u:

"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

so we see that there is a dedicated ELF extension for this.

Tested on GCC 7.4.0, Ubuntu 18.04.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
5

If it's going to be a constant then you should mark it as a constant - that's why 2 is bad in my opinion.

The compiler can use the const nature of the value to expand some of the maths, and indeed other operations that use the value.

The choice between 5 and 6 - hmm; 5 just feels better to me.

In 6) the value is unnecessarily detached from it's declaration.

I typically would have one or more of these headers that only defines constants etc within them, and then no other 'clever' stuff - nice lightweight headers that can easily be included anywhere.

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • 3
    (6) isn't unnecessary detachment, it's an intentional choice. If you have a lot of constants that are large, you waste a lot of space in the executable if you don't declare them as in (6). The can happen in math libraries... the waste may be under 100k but even that is important sometimes. (Some compilers have other ways to get around this, I think MSVC has a "once" attribute, or something similar.) – Dan Olson Feb 15 '10 at 23:37
  • With (5) you can't be really sure that it'll remain const (you could always cast away the constness). This is why I would still prefer the enum type. – fmuecke Feb 16 '10 at 10:51
  • @Dan Olson - that is a very good point - My answer was based on the fact that the type involved here is an int; but when dealing with bigger values the extern declaration is indeed a better plan. – Andras Zoltan Feb 16 '10 at 11:37
  • @fmuecke - Yes, you're right - in which case the enum value does prevent this. But does that mean we should always protect our values from writes in this way? If a programmer wants to abuse code, there's so many areas where a (target_type *)((void *)&value) cast can wreak havoc, that we can't catch, that sometimes we just have to place our trust in them; and indeed ourselves, no? – Andras Zoltan Feb 16 '10 at 11:46
  • 1
    @fmuecke A variable that is declared const cannot by changed by the program (attempting to do so is undefined behavior). const_cast is only defined in situations where the original variable was not declared const (for instance, passing a non-const value into a function as a const &). – David Stone Jan 23 '12 at 20:21
4

To answer your second question:

(2) is illegal because it violates the One Definition Rule. It defines GLOBAL_CONST_VAR in every file where it's included, i.e. more than once. (5) is legal because it's not subject to the One Definition Rule. Each GLOBAL_CONST_VAR is a separate definition, local to that file where it's included. All those definitions share the same name and value of course, but their addresses could differ.

MSalters
  • 173,980
  • 10
  • 155
  • 350
3
const int GLOBAL_CONST_VAR = 0xFF;

because it is a constant!

  • 1
    And it won't be treated like a macro, making debugging with it easier. – kayleeFrye_onDeck May 02 '17 at 22:33
  • -1, This will lead to redefinition warnings/errors when including the header in multiple source files. Also this answer is a duplicate of Nikolai Fetissov's answer. – lanoxx Sep 20 '18 at 08:10
1

It depends on your requirements. (5) is the best for most normal usage, but often results in the constant taking up storage space in every object file. (6) can get around this in situations where it's important.

(4) is also a decent choice if your priority is guaranteeing that storage space is never allocated, but it only works for integral constants of course.

Dan Olson
  • 22,849
  • 4
  • 42
  • 56
0
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
den bardadym
  • 2,747
  • 3
  • 25
  • 27