简体   繁体   English

在 C++ 中定义全局常量

[英]Defining global constant in C++

I want to define a constant in C++ to be visible in several source files.我想在 C++ 中定义一个常量以在多个源文件中可见。 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 (eg int get_GLOBAL_CONST_VAR() )一些函数返回值(例如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;在一个源文件中const int GLOBAL_CONST_VAR = 0xFF;

Option (1) - is definitely not the option you would like to use选项 (1) - 绝对不是您想使用的选项

Option (2) - defining instance of the variable in each object file using the header file选项 (2) - 使用头文件在每个目标文件中定义变量的实例

Option (3) - IMO is over killing in most cases选项 (3) - IMO 在大多数情况下过度杀戮

Option (4) - in many cases maybe not good since enum has no concrete type (C++0X will add possibility to define the type)选项 (4) - 在许多情况下可能不好,因为 enum 没有具体类型(C++0X 将增加定义类型的可能性)

So in most cases I need to choose between (5) and (6).所以在大多数情况下,我需要在(5)和(6)之间进行选择。 My questions:我的问题:

  1. What do you prefer (5) or (6)?你更喜欢(5)还是(6)?
  2. Why (5) is ok, while (2) is not?为什么(5)可以,而(2)不行?

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:绝对使用选项 5 - 它是类型安全的,并允许编译器优化(不要获取该变量的地址 :) 此外,如果它在标题中 - 将其粘贴到命名空间中以避免污染全局范围:

// 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;

(5) says exactly what you want to say. (5) 准确说出你想说的话。 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. (6) 另一方面,不会让编译器对其进行优化,因为编译器不知道您最终是否会更改它。

(5) is "better" than (6) because it defines GLOBAL_CONST_VAR as an Integral Constant Expression (ICE) in all translation units. (5) 比 (6) “更好”,因为它将GLOBAL_CONST_VAR定义为所有翻译单元中的积分常量表达式 (ICE)。 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.在 (6) 的情况下, GLOBAL_CONST_VAR将是仅在定义它的翻译单元中并且仅在定义点之后的 ICE。 In other translation units it won't work as ICE.在其他翻译单元中,它不会像 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, ie the &GLOBAL_CONST_VAR will give you a different pointer value in each translation unit.但是,请记住,(5)给出GLOBAL_CONST_VAR内部连接,这意味着的“地址身份” GLOBAL_CONST_VAR将在每个翻译单元不同,即&GLOBAL_CONST_VAR会给你在每个转换单元不同的指针值。 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.在大多数用例中,这无关紧要,但如果您需要一个具有一致全局“地址标识”的常量对象,那么您必须使用 (6),牺牲常量的 ICE-ness过程。

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).此外,当常数的 ICE-ness 不是问题(不是整数类型)并且类型的大小变大(不是标量类型)时,则(6)通常比(5)更好。

(2) is not OK because the GLOBAL_CONST_VAR in (2) has external linkage by default. (2) 不行,因为 (2) 中的GLOBAL_CONST_VAR默认有外部链接。 If you put it in header file, you'll usually end up with multiple definitions of GLOBAL_CONST_VAR , which is an error.如果你把它放在头文件中,你通常会得到GLOBAL_CONST_VAR多个定义,这是一个错误。 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).默认情况下,C++ 中的const对象具有内部链接,这就是为什么 (5) 起作用的原因(这就是为什么,正如我上面所说,您在每个翻译单元中获得一个单独的、独立的GLOBAL_CONST_VAR )。


Starting from C++17 you have an option of declaring从 C++17 开始,您可以选择声明

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.这在所有翻译单元中为您提供了一个 ICE(就像方法(5)),同时维护GLOBAL_CONST_VAR全局地址标识 - 在所有翻译单元中它将具有相同的地址。

如果您使用 C++11 或更高版本,请尝试使用编译时常量:

constexpr int GLOBAL_CONST_VAR{ 0xff };

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.如果它是一个常量,那么你应该将它标记为一个常量——这就是我认为 2 不好的原因。

The compiler can use the const nature of the value to expand some of the maths, and indeed other operations that use the value.编译器可以使用该值的 const 性质来扩展一些数学运算,以及使用该值的其他操作。

The choice between 5 and 6 - hmm; 5 和 6 之间的选择 - 嗯; 5 just feels better to me. 5只是让我感觉更好。

In 6) the value is unnecessarily detached from it's declaration.在 6) 中,该值不必要地与其声明分离。

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.我通常会有一个或多个这些头文件,它们只在其中定义常量等,然后没有其他“聪明”的东西——漂亮的轻量级头文件,可以很容易地包含在任何地方。

To answer your second question:回答你的第二个问题:

(2) is illegal because it violates the One Definition Rule. (2) 是非法的,因为它违反了单一定义规则。 It defines GLOBAL_CONST_VAR in every file where it's included, ie more than once.它在包含它的每个文件中定义GLOBAL_CONST_VAR ,即不止一次。 (5) is legal because it's not subject to the One Definition Rule. (5) 是合法的,因为它不受单一定义规则的约束。 Each GLOBAL_CONST_VAR is a separate definition, local to that file where it's included.每个GLOBAL_CONST_VAR都是一个单独的定义,位于包含它的那个文件的本地。 All those definitions share the same name and value of course, but their addresses could differ.当然,所有这些定义共享相同的名称和值,但它们的地址可能不同。

C++17 inline variables C++17 inline变量

This awesome C++17 feature allow us to:这个很棒的 C++17 特性允许我们:

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不是main.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp 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 . GitHub 上游.

See also: How do inline variables work?另请参阅: 内联变量如何工作?

C++ standard on inline variables内联变量的 C++ 标准

The C++ standard guarantees that the addresses will be the same. C++ 标准保证地址是相同的。 C++17 N4659 standard draft 10.1.6 "The inline specifier": C++17 N4659 标准草案10.1.6“内联说明符”:

6 An inline function or variable with external linkage shall have the same address in all translation units. 6 具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static is not given, then it has external linkage. cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果没有给出static ,那么它就有外部链接。

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 : man nmu

"u" The symbol is a unique global symbol. “u”符号是唯一的全局符号。 This is a GNU extension to the standard set of ELF symbol bindings.这是标准 ELF 符号绑定集的 GNU 扩展。 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.所以我们看到有一个专门的 ELF 扩展。

Tested on GCC 7.4.0, Ubuntu 18.04.在 GCC 7.4.0、Ubuntu 18.04 上测试。

const int GLOBAL_CONST_VAR = 0xFF;

因为它是一个常数!

#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

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. (5) 最适合大多数正常使用,但往往导致每个目标文件中不断占用存储空间。 (6) can get around this in situations where it's important. (6) 可以在重要的情况下解决这个问题。

(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. (4) 也是一个不错的选择,如果您的优先事项是保证永远不会分配存储空间,但它当然只适用于整数常量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM