Defining global constant in C++

前端 未结 10 1893
梦如初夏
梦如初夏 2020-11-29 16:44

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_
相关标签:
10条回答
  • 2020-11-29 17:05

    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;
    
    0 讨论(0)
  • 2020-11-29 17:05

    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.

    0 讨论(0)
  • 2020-11-29 17:06
    const int GLOBAL_CONST_VAR = 0xFF;
    

    because it is a constant!

    0 讨论(0)
  • 2020-11-29 17:06
    #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
    
    0 讨论(0)
  • 2020-11-29 17:17

    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.

    0 讨论(0)
  • 2020-11-29 17:23

    (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.

    0 讨论(0)
提交回复
热议问题