这是一个在 C++ 中非常常见但在标准 C 语言中不存在的关键字,很多人会混淆这两者,所以理解它们的区别至关重要。

核心结论:mutable 不是 C 语言的关键字
最重要的一点是:mutable 是 C++ 的一个关键字,不是标准 C 语言的一部分。
如果你在标准的 C 编译器(如 GCC 使用 c99 或 c11 标准,MSVC 的 /std:c11 等)中使用 mutable,编译器会报错,提示“未知类型”或“未定义的标识符”。
mutable 在 C++ 中是做什么的?
在 C++ 中,mutable 的主要作用是:允许一个成员变量的值可以被 const 成员函数修改。
为什么需要 mutable?—— const 成员函数的限制
在 C++ 中,如果一个成员函数被声明为 const,它承诺不会修改该对象的任何非静态成员变量,这是为了确保 const 对象的安全性,防止其状态被意外改变。

class MyClass {
public:
int value;
int* counter;
MyClass(int v) : value(v), counter(new int(0)) {}
// 这是一个 const 成员函数
void print() const {
std::cout << "Value: " << value << std::endl;
// value = 10; // 错误!不能修改非静态成员变量
// counter = nullptr; // 错误!同样不能修改
}
};
mutable 的解决方案
我们确实需要在 const 成员函数中修改一些变量,但这些变量的修改并不影响对象的核心逻辑或“状态”。
- 缓存计算结果。
- 记录某个函数被调用的次数。
- 在多线程环境中进行线程同步(如使用互斥锁)。
mutable 关键字就是为这种场景设计的,被 mutable 修饰的成员变量,即使在 const 成员函数中,也可以被修改。
mutable 的使用示例
让我们来看一个经典的缓存示例:
#include <iostream>
class Calculator {
private:
int last_result; // 存储上一次的计算结果
mutable bool cache_valid; // 标记缓存是否有效
mutable int cache; // 缓存值
public:
Calculator() : last_result(0), cache_valid(false), cache(0) {}
// 计算平方,这是一个 const 成员函数
int square(int x) const {
// 即使是 const 函数,我们也可以修改 mutable 变量
if (!cache_valid || x != last_result) {
cache = x * x; // 重新计算并更新缓存
cache_valid = true; // 标记缓存已更新
last_result = x;
}
std::cout << "Using cached value for " << x << std::endl;
return cache;
}
};
int main() {
const Calculator calc; // 创建一个 const 对象
std::cout << calc.square(5) << std::endl; // 第一次计算,缓存未命中
std::cout << calc.square(5) << std::endl; // 第二次,使用缓存
std::cout << calc.square(6) << std::endl; // 新的输入,缓存未命中
return 0;
}
代码解析:
- 我们有一个
const Calculator对象calc。 square函数被声明为const,因为它只读取输入x并返回结果,不改变对象的核心“计算能力”。- 为了提高效率,我们想缓存上一次的计算结果,这里
cache和cache_valid变量的修改,不应该破坏const对象的“逻辑常量性”(logical constness)。 - 通过将
cache_valid和cache声明为mutable,我们就可以在const成员函数square中安全地修改它们,实现了缓存功能,而不会违反const的承诺。
C 语言中如何实现类似 mutable 的效果?
既然 C 语言没有 mutable,如果遇到类似的需求(在某个函数中需要修改一个“看起来不应该变”的变量),我们可以采用以下几种方法:
使用 const 修饰指针(模拟 const 成员函数)
在 C 中,const 更多是用来修饰指针和变量,而不是像 C++ 那样作用于成员函数,我们可以模拟一个 const 成员函数的行为。
#include <stdio.h>
// 结构体相当于 C++ 的类
typedef struct {
int value;
int* counter; // 模拟一个需要修改的“内部状态”
} Data;
// “const” 成员函数:通过 const 指针访问
void print_data(const Data* d) {
printf("Value: %d\n", d->value);
// d->value = 10; // 错误!不能通过 const 指针修改
}
// 一个需要修改内部状态的函数(非 const)
void update_counter(Data* d) {
if (d->counter) {
(*d->counter)++;
}
}
int main() {
Data my_data = {42, &(int){0}};
Data* const p_data = &my_data; // 指针本身是 const,指向的 Data 可变
print_data(p_data); // 安全地读取
update_counter(p_data); // 明确调用一个可以修改的函数
printf("Counter is now: %d\n", *my_data.counter);
return 0;
}
在这个 C 示例中,print_data 接收一个 const Data*,这模拟了 C++ 中 const 成员函数的行为,它承诺不会通过这个指针修改 Data 的内容,而 update_counter 是一个普通的函数,可以修改 Data,C++ 的 mutable 将这两种行为合并在一个函数中,而 C 语言则需要通过函数签名来区分。
使用全局变量或静态变量(不推荐,破坏封装性)
如果只是想记录一个调用次数,可以使用全局变量或函数内的静态变量,但这会破坏数据的封装性,通常不推荐。
#include <stdio.h>
void function_with_counter() {
static int call_count = 0; // 静态变量,生命周期是整个程序
call_count++;
printf("Function called %d times.\n", call_count);
}
int main() {
function_with_counter(); // 1
function_with_counter(); // 2
function_with_counter(); // 3
return 0;
}
使用 void* 上下文指针(回调函数常用)
在一些高级 API(如回调函数)中,会传递一个 void* 指针作为上下文,让用户可以在回调中修改外部数据。
#include <stdio.h>
void process_data(void* context) {
int* counter = (int*)context;
(*counter)++;
}
int main() {
int my_counter = 0;
process_data(&my_counter); // 传递上下文
printf("Counter is now: %d\n", my_counter); // 1
return 0;
}
| 特性 | C++ mutable |
C 语言 |
|---|---|---|
| 存在性 | 是 C++ 的关键字 | 不是 C 语言的关键字 |
| 核心作用 | 允许 const 成员函数修改特定成员变量 |
无直接对应物 |
| 设计哲学 | 支持逻辑常量性,允许在 const 函数中修改不影响对象状态的变量(如缓存、计数器) |
通过函数签名(const 指针)来区分“只读”和“读写”操作 |
| 实现方式 | 在成员变量前加 mutable 关键字 |
使用 const 修饰指针、全局变量、静态变量或 void* 上下文等 |
| 推荐用法 | 用于实现缓存、线程同步等,保持 const 成员函数的纯净性 |
严格区分“只读”和“读写”函数,避免使用全局变量破坏封装性 |
如果你在写 C++ 代码,并且需要在 const 成员函数里修改一些“无关紧要”的变量,mutable 是你的不二之选,如果你在写 C 代码,你需要换一种思路,通过函数签名和指针来明确表达数据的可变性。
