[英]Defining global constant in C++
我想在 C++ 中定义一个常量以在多个源文件中可见。 我可以想象以下方法在头文件中定义它:
#define GLOBAL_CONST_VAR 0xFF
int GLOBAL_CONST_VAR = 0xFF;
int get_GLOBAL_CONST_VAR()
)enum { GLOBAL_CONST_VAR = 0xFF; }
const int GLOBAL_CONST_VAR = 0xFF;
extern const int GLOBAL_CONST_VAR;
在一个源文件中const int GLOBAL_CONST_VAR = 0xFF;
选项 (1) - 绝对不是您想使用的选项
选项 (2) - 使用头文件在每个目标文件中定义变量的实例
选项 (3) - IMO 在大多数情况下过度杀戮
选项 (4) - 在许多情况下可能不好,因为 enum 没有具体类型(C++0X 将增加定义类型的可能性)
所以在大多数情况下,我需要在(5)和(6)之间进行选择。 我的问题:
绝对使用选项 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) 准确说出你想说的话。 此外,它可以让编译器在大部分时间将其优化掉。 (6) 另一方面,不会让编译器对其进行优化,因为编译器不知道您最终是否会更改它。
(5) 比 (6) “更好”,因为它将GLOBAL_CONST_VAR
定义为所有翻译单元中的积分常量表达式 (ICE)。 例如,您将能够在所有翻译单元中将其用作数组大小和大小写标签。 在 (6) 的情况下, GLOBAL_CONST_VAR
将是仅在定义它的翻译单元中并且仅在定义点之后的 ICE。 在其他翻译单元中,它不会像 ICE 那样工作。
但是,请记住,(5)给出GLOBAL_CONST_VAR
内部连接,这意味着的“地址身份” GLOBAL_CONST_VAR
将在每个翻译单元不同,即&GLOBAL_CONST_VAR
会给你在每个转换单元不同的指针值。 在大多数用例中,这无关紧要,但如果您需要一个具有一致全局“地址标识”的常量对象,那么您必须使用 (6),牺牲常量的 ICE-ness过程。
此外,当常数的 ICE-ness 不是问题(不是整数类型)并且类型的大小变大(不是标量类型)时,则(6)通常比(5)更好。
(2) 不行,因为 (2) 中的GLOBAL_CONST_VAR
默认有外部链接。 如果你把它放在头文件中,你通常会得到GLOBAL_CONST_VAR
多个定义,这是一个错误。 默认情况下,C++ 中的const
对象具有内部链接,这就是为什么 (5) 起作用的原因(这就是为什么,正如我上面所说,您在每个翻译单元中获得一个单独的、独立的GLOBAL_CONST_VAR
)。
从 C++17 开始,您可以选择声明
inline extern const int GLOBAL_CONST_VAR = 0xFF;
在头文件中。 这在所有翻译单元中为您提供了一个 ICE(就像方法(5)),同时维护GLOBAL_CONST_VAR
全局地址标识 - 在所有翻译单元中它将具有相同的地址。
如果您使用 C++11 或更高版本,请尝试使用编译时常量:
constexpr int GLOBAL_CONST_VAR{ 0xff };
如果它是一个常量,那么你应该将它标记为一个常量——这就是我认为 2 不好的原因。
编译器可以使用该值的 const 性质来扩展一些数学运算,以及使用该值的其他操作。
5 和 6 之间的选择 - 嗯; 5只是让我感觉更好。
在 6) 中,该值不必要地与其声明分离。
我通常会有一个或多个这些头文件,它们只在其中定义常量等,然后没有其他“聪明”的东西——漂亮的轻量级头文件,可以很容易地包含在任何地方。
回答你的第二个问题:
(2) 是非法的,因为它违反了单一定义规则。 它在包含它的每个文件中定义GLOBAL_CONST_VAR
,即不止一次。 (5) 是合法的,因为它不受单一定义规则的约束。 每个GLOBAL_CONST_VAR
都是一个单独的定义,位于包含它的那个文件的本地。 当然,所有这些定义共享相同的名称和值,但它们的地址可能不同。
C++17 inline
变量
这个很棒的 C++17 特性允许我们:
constexpr
: 如何声明 constexpr extern?主程序
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
不是main.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 ¬main_i;
}
编译并运行:
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
另请参阅: 内联变量如何工作?
内联变量的 C++ 标准
C++ 标准保证地址是相同的。 C++17 N4659 标准草案10.1.6“内联说明符”:
6 具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。
cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果没有给出static
,那么它就有外部链接。
内联变量实现
我们可以观察它是如何实现的:
nm main.o notmain.o
其中包含:
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
man nm
说u
:
“u”符号是唯一的全局符号。 这是标准 ELF 符号绑定集的 GNU 扩展。 对于这样的符号,动态链接器将确保在整个过程中只有一个具有此名称和类型的符号在使用。
所以我们看到有一个专门的 ELF 扩展。
在 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
这取决于您的要求。 (5) 最适合大多数正常使用,但往往导致每个目标文件中不断占用存储空间。 (6) 可以在重要的情况下解决这个问题。
(4) 也是一个不错的选择,如果您的优先事项是保证永远不会分配存储空间,但它当然只适用于整数常量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.