在 C++ 中,一个定义了全局变量的头文件被多个源文件包含时,每个源文件都会创建该全局变量的一个实例,这可能导致链接时出现重定义错误,代码示例如下。
// 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时,为了在宏内使用封装的对象,定义了全局变量,由于该日志类头文件被多个文件包含出现了链接错误。简化后的代码如下:
//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可以修改为内敛变量,如下
// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H
inline int globalVar = 42;
#endif // EXAMPLE_H
使用场景
1. 头文件中定义全局变量,保证变量定义的唯一性
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
inline constexpr double PI = 3.14159265358979323846;
inline constexpr std::string AppName = "MyApp";
#endif // CONSTANTS_H
2. 类的静态成员变量
可以用来定义模板类的内联静态成员变量,也可以用来定义普通类的内联静态成员,只是普通类的静态成员变量通常来讲定义在源文件内,没必要内联。
// 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将变量声明为内联变量,可以确保在多个源文件中只有一个变量实例,避免了链接时的重定义错误。然而,仍需要谨慎使用内联变量,并注意其初始化和定义的位置,以确保程序的正确性和可维护性。