提问人:Jan Schultke 提问时间:8/29/2023 更新时间:8/31/2023 访问量:362
全局变量 - 何时使用 static、inline、extern、const 和 constexpr
Global variables - When to use static, inline, extern, const, and constexpr
问:
有很多与 C++ 全局变量相关的问题和答案,例如:
- C 和 C++ 中的静态和外部全局变量
- 全局变量和 constepr(内联与否?
- 将静态全局变量声明为内联有什么意义吗?
- 全局变量是默认的 extern,还是等同于在全局变量中使用 extern 声明变量?
- 在 C++1z 中定义全局常量?
- 在 C++ 中定义全局常量
- 内联静态 constexpr 与全局内联 constexpr
它们都包含小片段的信息,例如变量在 C++17 中的具体工作方式等。其中一些也没有很好地老化,因为 C++17 通过引入变量从根本上改变了规则。inline
inline
C++ 还引入了模块和模块链接,再次使 StackOverflow 上的大多数内容过时,甚至过时。
本问答试图将这些问题统一起来,并为读者提供考虑这些版本更改的概述。
问题
- 何时对全局变量使用 、 、 、 等?
static
inline
extern
const
constexpr
- 答案在历史上是如何变化的(C++17之前/之后,C++20之前/之后)?
- 答案如何根据全局变量的位置(标头/源/模块)而变化?
答:
何时对全局变量使用 、 、 、 等?
static
inline
extern
const
constexpr
0. 概述
全局变量用例 | 常数 | 非常量 |
---|---|---|
单个源文件的 本地文件(即声明且仅在 单个文件中使用,未在标头中声明) |
static const ,(C++11)或 匿名命名空间(C++11) static constexpr const |
static 或匿名命名空间(C++11) |
已声明,未在标头中定义, 在源文件中定义 |
extern const 在标题中;在源代码中,或者 (C++11)在源代码中 const constexpr |
extern 在标题中;源代码中是普通的 |
在标头中定义, 直到 C++17 |
模仿模板 和/或 (C++11);或 仅适用于整数 inline const constexpr enum |
使用模板进行模仿inline |
在头文件中定义, 从 C++17 |
inline const 或inline constexpr |
inline |
单个模块的本地(C++20) | const 或;选择 constexpr inline |
选择inline |
由模块导出(C++20) | export const 或export inline constexpr |
export ;选择 inline |
在上述所有情况下,(从 C++20 开始)也可以使用,但不能与 结合使用。 也有用,和不一样。constinit
constexpr
constinit const
constexpr
注意:该决定也可能根据全局变量是否在动态链接库中定义/使用以及其他因素而改变。
1.始终使用确保一个源文件本地的所有内容都具有内部链接
首先,如果全局变量是在单个源文件中声明的,并且只在单个源文件中使用,那么它必须具有内部链接。 在以下情况下,全局变量具有内部链接:
- 它被标记为
static
- 它位于匿名命名空间中(从 C++11 开始)
- 它是(或(自 C++11 以来),因为暗示
const
constexpr
constexpr
const
)
如果它没有内部链接,那么你很容易遇到ODR违规。以下示例格式不正确,无需诊断。
// a.cpp
int counter = 0; // FIXME: surround with anonymous namespace, or add 'static'
// b.cpp
long counter = 0; // FIXME: surround with anonymous namespace, or add 'static'
创建变量(从 C++17 开始)并不能解决此问题。
内部链接使其安全,因为每个 TU 中的两个 s 是不同的。inline
counter
2. 如果声明了某些内容,但未在标头中定义,请将其设为extern
有时,每个人都有一个定义并不重要。例如:
// log.hpp
extern std::ofstream log_file;
// log.cpp
std::ofstream log_file = open_log_file();
将 to 的定义放在标题中是没有意义的,因此在任何地方都可见。在性能方面可以获得很少的收益,这将迫使我们也在任何地方都可见。log_file
open_log_file()
注意(自 C++11 起)或(自 C++20 起)extern constexpr
extern const constinit
另一个有效但罕见的用例是(自 C++11 以来)(请参阅此答案),即 在标头中,(自 C++11 起)在源中。
如果可用,则通过源代码中的(从 C++20 开始)更好地表达这一点。extern constexpr
extern const
constexpr
const constinit
此模式的目的是避免对初始化成本高昂的查找表进行动态初始化,同时保持标头/源拆分。
3. 如果在标头中定义了某些内容,请制作它(从 C++17 开始),或使用模板进行模仿(直到 C++17) inline
inline
有时,在任何地方都有一个定义是很重要的,例如对于应该内联的全局常量:
inline constexpr float exponent = 1.25f;
问:我真的需要与?inline
constexpr
是的,你有。请看以下示例:
constexpr float exponent = 1.25f; // OK so far, but dangerous
// note: 'const float&' might seem contrived because you could work with 'float'.
// However, templates use const& everywhere, so it's very easy to
// run into this case indirectly.
inline const float& foo(const float& x) {
// IFNDR if the definition of foo appears in multiple translation units (TUs).
return std::max(x, exponent);
}
该程序可能格式不正确,不需要诊断,因为具有内部链接(由于存在),并且是每个 TU 中的一个不同对象。
每个定义都可能返回对自己唯一值的不同引用,这违反了 [basic.def.odr] p14.5exponent
const
foo
exponent
问:在 C++17 之前我可以做什么? 变量尚不存在。inline
类似的机制一直以模板的形式存在。
// define a wrapper class template with a static data member
template <typename = void>
struct helper { static const float exponent; };
// define the static data member
template <typename T>
struct helper<T>::exponent = 1.25f;
// For convenience, make a reference with internal linkage to it.
// This is safe from ODR violations because
// [basic.def.odr] p14.5.2 has a special case for it, and
// this special case was retroactively applied to all C++ standards
// in the form of a defect report.
static constexpr float& exponent = helper<>::exponent;
// (static const float& prior to C++11)
或者,特别是对于作用域(自 C++11 起)和无作用域枚举,可以将定义放在标头中而不会有风险:
inline constexpr int array_size = 100; // since C++17
enum { array_size = 100; }; // pre-C++17 alternative
4. 尽可能制作所有全局变量甚至(从 C++11 开始)const
constexpr
除了 1.、2.、3 中的规则之外,总是尽可能地做事。
这极大地简化了确保程序正确性的过程,并实现了额外的编译器优化。const
问:如果初始化很复杂,我该怎么办?
有时你不能只用一个简单的表达式来初始化一个全局变量,就像下面这样:
std::array<float, 100> lookup;
void init() {
for (std::size_t i = 0; i < lookup.size(); ++i) {
lookup[i] = compute(i);
}
}
但是,不要延迟初始化;而是使用立即调用的 lambda 表达式 (IILE)(自 C++11 起)或常规函数来执行初始化。
constexpr std::array<float, 100> lookup = [] {
decltype(lookup) result{};
/* ... */
return result;
}();
问:OR(自 C++11 以来)对链接有什么影响?const
constexpr
制作一个全局变量 or(从 C++11 开始)给了它内部链接,但正如 3 中解释的那样,这并不能为您简化任何事情。
您仍然需要担心链接和 ODR。const
constexpr
5. 静态数据成员的工作方式不同
如 3 所示,静态数据成员可能不遵循相同的规则。 它们与它们所属的类具有相同的联系,其后果如下:
- 在类模板的情况下,静态数据成员是准的。
inline
const
并不意味着静态数据成员的内部链接。
此外,对于静态数据成员意味着(自 C++17 起)。constexpr
inline
上一个:运算符的优先级和关联性是什么?
评论
inline constexpr <type>{value}
constexpr <type>{value};
const
static constexpr <type>{value};
inline
static
constexpr