在 C 中,一个定义了全局变量的头文件被多个源文件包含时,每个源文件都会创建该全局变量的一个实例,这可能导致链接时出现重定义错误,代码示例如下。
代码语言:javascript复制// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
int globalVar = 42;
#endif // EXAMPLE_H
// file1.cpp
#include "example.h"
// Some code using globalVar
// file2.cpp
#include "example.h"
// Some other code using globalVar
头文件中的变量globalVar会在file1.cpp和file2.cpp中分别创建实例,出现重定义的链接错误,MSVC的错误为LNK2005和LNK1169。
这是小编遇到的实际问题,在封装spdlog时,为了在宏内使用封装的对象,定义了全局变量,由于该日志类头文件被多个文件包含出现了链接错误。简化后的代码如下:
代码语言:javascript复制//log.h
Log logger;
// Define macros for different log levels
#define LOG_DEBUG(msg) logger.Log(LogLevel::Debug,__FUNCTION__,__LINE__, msg)
为了解决全局变量的重定义问题,C 17引入了内联变量的概念。
内联变量
使用 inline 关键字可以将变量声明为内联变量,在多个源文件中包含该头文件时,编译器只会创建一个该变量的实例。
如上实例中定义的全局变量globalVar可以修改为内敛变量,如下
代码语言:javascript复制// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
inline int globalVar = 42;
#endif // EXAMPLE_H
使用场景
1. 头文件中定义全局变量,保证变量定义的唯一性
代码语言:javascript复制// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
inline constexpr double PI = 3.14159265358979323846;
inline constexpr std::string AppName = "MyApp";
#endif // CONSTANTS_H
2. 类的静态成员变量
可以用来定义模板类的内联静态成员变量,也可以用来定义普通类的内联静态成员,只是普通类的静态成员变量通常来讲定义在源文件内,没必要内联。
代码语言:javascript复制// template_class.h
#ifndef TEMPLATE_CLASS_H
#define TEMPLATE_CLASS_H
template<typename T>
class MyClass {
public:
static inline T defaultValue = T();
};
#endif // TEMPLATE_CLASS_H
注意事项
内联变量有诸多的便利性,但是使用时仍需注意以下几点:
- 必须在定义处进行初始化:内联变量的初始化必须在声明处完成。
- 不要在多个源文件中定义相同的内联变量:虽然编译器只会保留一个实例,但仍然不建议在多个源文件中定义相同的内联变量,以避免混乱和不必要的复杂性。
- 不要过度使用内联变量,每个编译单元都会维护一个内联变量的副本,如果定义过多的内联变量,不仅会导致程序占用大量的内存空间,也会增加编译时间。
总结
内联变量是 C 17 新增的特性,用于解决头文件中变量多实例化的问题。通过使用inline将变量声明为内联变量,可以确保在多个源文件中只有一个变量实例,避免了链接时的重定义错误。然而,仍需要谨慎使用内联变量,并注意其初始化和定义的位置,以确保程序的正确性和可维护性。