[Modern CPP]内联变量——保证变量唯一性的利器

2024-07-18 13:28:02 浏览数 (2)

在 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将变量声明为内联变量,可以确保在多个源文件中只有一个变量实例,避免了链接时的重定义错误。然而,仍需要谨慎使用内联变量,并注意其初始化和定义的位置,以确保程序的正确性和可维护性。

0 人点赞