Skip to content

C++ 存储类

C++ 中的存储类说明符(storage class specifiers)定义了变量和函数的生命期(duration)和链接性(linkage,即在翻译单元之间的可见性)。它们位于所修饰类型之前。主要的存储类说明符有:

  • auto (自 C++11 起:用于类型推断,而非存储类)
  • register (已弃用)
  • static
  • extern
  • mutable
  • thread_local (自 C++11 起)

在现代 C++(C++11 及更高版本)中,auto 不是存储类说明符。相反,它用于自动类型推断(automatic type deduction)。编译器根据变量的初始化表达式推断其类型。未与其它说明符一起声明为 auto 的变量默认具有自动存储期(automatic storage duration)(就像局部变量一样)。

// C++11 及更高版本
#include <vector>
int main() {
auto count = 10; // count 被推断为 int
auto pi = 3.14159; // pi 被推断为 double
auto message = "Hello"; // message 被推断为 const char*
std::vector<int> numbers = {1, 2, 3};
auto it = numbers.begin(); // it 被推断为 std::vector<int>::iterator
// auto 可以与 const, &, &&, * 结合使用
const auto MAX_SIZE = 100; // const int
auto& ref_count = count; // int&
}

auto 简化了代码,尤其在使用迭代器或 lambda 表达式等复杂类型时。auto 的原始含义(显式声明自动存储期)已经过时。

register 关键字在历史上是向编译器发出的一个提示,建议将变量存储在 CPU 寄存器中以加快访问速度。然而,现代编译器在优化和寄存器分配方面表现出色,这使得该关键字在很大程度上已过时且无效。它在 C++17 中被弃用(deprecated),强烈不建议使用。编译器通常会忽略它。

// 避免使用 'register'
// register int counter; // 现代编译器会忽略此提示

请依赖编译器优化,而不是使用 register。

static 关键字根据上下文具有不同的含义:

  • 在函数/块内部(静态局部变量): 变量在函数调用或块进入之间保留其值。它只初始化一次(自 C++11 起线程安全初始化)。它具有静态存储期(static storage duration),但具有块作用域(block scope)。
  • 在命名空间作用域(全局变量/函数): 它赋予变量或函数内部链接性(internal linkage)。这意味着它只在当前的翻译单元(定义它的 .cpp 文件)内可见。这有助于避免文件之间的命名冲突。
  • 在类定义内部(静态类成员): 创建一个属于类本身而非任何特定对象实例的成员变量或函数。类的所有对象共享相同的静态成员变量。静态成员函数可以使用类名调用(ClassName::staticFunc();),并且只能访问其他静态成员。

演示静态局部变量和全局变量的示例:

#include <iostream>
// 静态全局变量(内部链接性)
// 仅在此文件 (cpp_storage_classes.cpp) 内可见
static int file_scope_count = 10;
// 函数声明
void func();
int main() {
while (file_scope_count > 0) {
func();
file_scope_count--; // 修改文件作用域变量
}
return 0;
}
// 函数定义
void func() {
// 静态局部变量
// 只初始化一次,在函数调用之间保留值
static int i = 5;
i++;
std::cout << "i is " << i;
std::cout << " and file_scope_count is " << file_scope_count << std::endl;
}

编译并执行后,将产生以下输出:

i is 6 and file_scope_count is 10
i is 7 and file_scope_count is 9
i is 8 and file_scope_count is 8
i is 9 and file_scope_count is 7
i is 10 and file_scope_count is 6
i is 11 and file_scope_count is 5
i is 12 and file_scope_count is 4
i is 13 and file_scope_count is 3
i is 14 and file_scope_count is 2
i is 15 and file_scope_count is 1

extern 关键字用于声明在另一个翻译单元(translation unit,另一个 .cpp 文件)或同一文件稍后位置定义的变量或函数。它主要影响链接性(linkage)。

  • 对于变量: 它声明一个变量但不定义它(不分配内存)。它告诉编译器,存在一个同名同类型的变量在其他地方(通常在另一个 .cpp 文件中)定义,并且具有外部链接性(external linkage,这是未声明为 static 的全局变量的默认链接性)。你不能在 extern 声明中初始化变量。
  • 对于函数: 声明函数为 extern 通常是冗余的,因为函数默认具有外部链接性,除非声明为 static。有时为了清晰起见会使用它。
  • 链接性规范: extern "C" 用于声明应使用 C 链接约定(C linkage conventions)的函数,允许 C++ 代码与 C 库交互。

使用两个文件共享全局变量的示例:

文件 1: main.cpp

#include <iostream>
// 声明在其他地方定义的 'shared_counter'
extern int shared_counter;
// 声明在其他地方定义的函数
extern void print_counter();
int main() {
shared_counter = 5; // 修改共享变量
std::cout << "main.cpp: Set shared_counter to " << shared_counter << std::endl;
print_counter(); // 调用另一个文件中的函数
return 0;
}

文件 2: support.cpp

#include <iostream>
// 全局变量的定义(默认外部链接性)
int shared_counter = 0; // 在这里进行初始化
// 函数定义
void print_counter() {
std::cout << "support.cpp: shared_counter is " << shared_counter << std::endl;
}

编译并链接这些文件(使用 g++ 的示例):

g++ main.cpp support.cpp -o shared_var_example -std=c++11

运行 ./shared_var_example 将产生:

main.cpp: Set shared_counter to 5
support.cpp: shared_counter is 5

mutable 说明符仅适用于类的非静态、非常量数据成员。它允许即使对象被声明为 const,或者通过 const 成员函数访问时,也能修改该成员变量。这通常用于不影响对象可观察状态(observable state)的内部状态,例如缓存或互斥锁(mutexes)。

class Example {
mutable int cache_value; // 可以被 const 方法修改
int regular_value;
mutable bool cache_valid = false; // 将其也设为 mutable
public:
Example(int val) : regular_value(val) {}
int getValue() const { // 这是一个 const 成员函数
if (!cache_valid) {
// 模拟耗时计算
cache_value = regular_value * 2; // OK: cache_value 是 mutable 的
cache_valid = true; // OK: cache_valid 是 mutable 的
}
// regular_value = 10; // 错误:不能在 const 函数中修改非 mutable 成员
return cache_value;
}
};

thread_local 说明符声明一个变量具有线程存储期(thread storage duration)。这意味着程序中的每个线程都将拥有该变量自己的独立实例。它对于线程特定数据(thread-specific data)非常有用,例如每线程的错误码或随机数生成器。

#include <iostream>
#include <thread>
#include <vector>
// 每个线程都有自己的 thread_id 副本
thread_local int thread_specific_id = 0;
int global_id_counter = 0; // 共享计数器
void worker_thread(int id) {
thread_specific_id = id; // 设置此线程的 ID
global_id_counter++;
std::cout << "Thread " << std::this_thread::get_id()
<< ": thread_specific_id = " << thread_specific_id
<< ", global_id_counter = " << global_id_counter << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 1; i <= 3; ++i) {
threads.emplace_back(worker_thread, i * 100);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Main thread: thread_specific_id = " << thread_specific_id
<< ", global_id_counter = " << global_id_counter << std::endl;
return 0;
}

输出可能显示不同的 thread_specific_id 值,但 global_id_counter 会递增,这表明每个线程都有其自己的 thread_specific_id。