Define constant variables in C++ header

前端 未结 6 1355
既然无缘
既然无缘 2020-11-30 18:30

A program I am working on has many constants that apply throughout all classes. I want to make one header file \"Constants.h\", and be able to declare all the relevant const

相关标签:
6条回答
  • 2020-11-30 18:56

    You could simply define a series of const ints in a header file:

    // Constants.h
    #if !defined(MYLIB_CONSTANTS_H)
    #define MYLIB_CONSTANTS_H 1
    
    const int a = 100;
    const int b = 0x7f;
    
    #endif
    

    This works because in C++ a name at namespace scope (including the global namespace) that is explicitly declared const and not explicitly declared extern has internal linkage, so these variables would not cause duplicate symbols when you link together translation units. Alternatively you could explicitly declare the constants as static.

    static const int a = 100;
    static const int b = 0x7f;
    

    This is more compatible with C and more readable for people that may not be familiar with C++ linkage rules.

    If all the constants are ints then another method you could use is to declare the identifiers as enums.

    enum mylib_constants {
        a = 100;
        b = 0x7f;
    };
    

    All of these methods use only a header and allow the declared names to be used as compile time constants. Using extern const int and a separate implementation file prevents the names from being used as compile time constants.


    Note that the rule that makes certain constants implicitly internal linkage does apply to pointers, exactly like constants of other types. The tricky thing though is that marking a pointer as const requires syntax a little different that most people use to make variables of other types const. You need to do:

    int * const ptr;
    

    to make a constant pointer, so that the rule will apply to it.

    Also note that this is one reason I prefer to consistently put const after the type: int const instead of const int. I also put the * next to the variable: i.e. int *ptr; instead of int* ptr; (compare also this discussion).

    I like to do these sorts of things because they reflect the general case of how C++ really works. The alternatives (const int, int* p) are just special cased to make some simple things more readable. The problem is that when you step out of those simple cases, the special cased alternatives become actively misleading.

    So although the earlier examples show the common usage of const, I would actually recommend people write them like this:

    int const a = 100;
    int const b = 0x7f;
    

    and

    static int const a = 100;
    static int const b = 0x7f;
    
    0 讨论(0)
  • 2020-11-30 18:59

    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.

    C++17 standard draft on "global" const implies static

    This is the quote for what was mentioned at: https://stackoverflow.com/a/12043198/895245

    C++17 n4659 standard draft 6.5 "Program and linkage":

    3 A name having namespace scope (6.3.6) has internal linkage if it is the name of

    • (3.1) — a variable, function or function template that is explicitly declared static; or,
    • (3.2) — a non-inline variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage; or
    • (3.3) — a data member of an anonymous union.

    "namespace" scope is what we colloquially often refer to as "global".

    Annex C (informative) Compatibility, C.1.2 Clause 6: "basic concepts" gives the rationale why this was changed from C:

    6.5 [also 10.1.7]

    Change: A name of file scope that is explicitly declared const, and not explicitly declared extern, has internal linkage, while in C it would have external linkage.

    Rationale: Because const objects may be used as values during translation in C++, this feature urges programmers to provide an explicit initializer for each const object. This feature allows the user to put const objects in source files that are included in more than one translation unit.

    Effect on original feature: Change to semantics of well-defined feature.

    Difficulty of converting: Semantic transformation.

    How widely used: Seldom.

    See also: Why does const imply internal linkage in C++, when it doesn't in C?

    Tested in GCC 7.4.0, Ubuntu 18.04.

    0 讨论(0)
  • 2020-11-30 19:05

    It seems that bames53's answer can be extended to defining integer and non-integer constant values in namespace and class declarations even if they get included in multiple source files. It is not necessary to put the declarations in a header file but the definitions in a source file. The following example works for Microsoft Visual Studio 2015, for z/OS V2.2 XL C/C++ on OS/390, and for g++ (GCC) 8.1.1 20180502 on GNU/Linux 4.16.14 (Fedora 28). Note that the constants are declared/defined in only a single header file that gets included in multiple source files.

    In foo.cc:

    #include <cstdio>               // for puts
    
    #include "messages.hh"
    #include "bar.hh"
    #include "zoo.hh"
    
    int main(int argc, const char* argv[])
    {
      puts("Hello!");
      bar();
      zoo();
      puts(Message::third);
      return 0;
    }
    

    In messages.hh:

    #ifndef MESSAGES_HH
    #define MESSAGES_HH
    
    namespace Message {
      char const * const first = "Yes, this is the first message!";
      char const * const second = "This is the second message.";
      char const * const third = "Message #3.";
    };
    
    #endif
    

    In bar.cc:

    #include "messages.hh"
    #include <cstdio>
    
    void bar(void)
    {
      puts("Wow!");
      printf("bar: %s\n", Message::first);
    }
    

    In zoo.cc:

    #include <cstdio>
    #include "messages.hh"
    
    void zoo(void)
    {
      printf("zoo: %s\n", Message::second);
    }
    

    In bar.hh:

    #ifndef BAR_HH
    #define BAR_HH
    
    #include "messages.hh"
    
    void bar(void);
    
    #endif
    

    In zoo.hh:

    #ifndef ZOO_HH
    #define ZOO_HH
    
    #include "messages.hh"
    
    void zoo(void);
    
    #endif
    

    This yields the following output:

    Hello!
    Wow!
    bar: Yes, this is the first message!
    zoo: This is the second message.
    Message #3.
    

    The data type char const * const means a constant pointer to an array of constant characters. The first const is needed because (according to g++) "ISO C++ forbids converting a string constant to 'char*'". The second const is needed to avoid link errors due to multiple definitions of the (then insufficiently constant) constants. Your compiler might not complain if you omit one or both of the consts, but then the source code is less portable.

    0 讨论(0)
  • 2020-11-30 19:16

    I like the namespace better for this kind of purpose.

    Option 1 :

    #ifndef MYLIB_CONSTANTS_H
    #define MYLIB_CONSTANTS_H
    
    //  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
    namespace LibConstants
    {
      const int CurlTimeOut = 0xFF;     // Just some example
      ...
    }
    #endif
    
    // source.cpp
    #include <LibConstants.hpp>
    int value = LibConstants::CurlTimeOut;
    

    Option 2 :

    #ifndef MYLIB_CONSTANTS_H
    #define MYLIB_CONSTANTS_H
    //  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
    namespace CurlConstants
    {
      const int CurlTimeOut = 0xFF;     // Just some example
      ...
    }
    
    namespace MySQLConstants
    {
      const int DBPoolSize = 0xFF;      // Just some example
      ...
    }
    #endif
    
    
    
    // source.cpp
    #include <LibConstants.hpp>
    int value = CurlConstants::CurlTimeOut;
    int val2  = MySQLConstants::DBPoolSize;
    

    And I would never use a Class to hold this type of HardCoded Const variables.

    0 讨论(0)
  • 2020-11-30 19:18

    You generally shouldn't use e.g. const int in a header file, if it's included in several source files. That is because then the variables will be defined once per source file (translation units technically speaking) because global const variables are implicitly static, taking up more memory than required.

    You should instead have a special source file, Constants.cpp that actually defines the variables, and then have the variables declared as extern in the header file.

    Something like this header file:

    // Protect against multiple inclusions in the same source file
    #ifndef CONSTANTS_H
    #define CONSTANTS_H
    
    extern const int CONSTANT_1;
    
    #endif
    

    And this in a source file:

    const int CONSTANT_1 = 123;
    
    0 讨论(0)
  • 2020-11-30 19:19

    Rather than making a bunch of global variables, you might consider creating a class that has a bunch of public static constants. It's still global, but this way it's wrapped in a class so you know where the constant is coming from and that it's supposed to be a constant.

    Constants.h

    #ifndef CONSTANTS_H
    #define CONSTANTS_H
    
    class GlobalConstants {
      public:
        static const int myConstant;
        static const int myOtherConstant;
    };
    
    #endif
    

    Constants.cpp

    #include "Constants.h"
    
    const int GlobalConstants::myConstant = 1;
    const int GlobalConstants::myOtherConstant = 3;
    

    Then you can use this like so:

    #include "Constants.h"
    
    void foo() {
      int foo = GlobalConstants::myConstant;
    }
    
    0 讨论(0)
提交回复
热议问题