mutable 关键字
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 30 课,共 104 课。
你已经学过,const 成员函数不能修改任何成员变量。但有时你需要修改不会影响对象逻辑状态的成员,比如缓存或访问计数器。mutable 关键字可以作为 const 限制的例外。
可变成员即使在 const 成员函数中也可以修改:
class DataFetcher {
std::string data;
mutable int accessCount; // 可以在 const 函数中修改
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // 可以这样做,因为 accessCount 是 mutable 的
return data;
}
int getAccessCount() const {
return accessCount;
}
};如果没有 mutable,在常量函数 getData() 中递增 accessCount 会导致编译器错误。mutable 关键字告诉编译器:“即使对象被视为常量,也允许更改这个成员。”
mutable 的常见用途包括缓存计算结果、跟踪访问统计信息,以及实现互斥锁等同步原语。在每种情况下,修改都不会改变对象在逻辑上所代表的内容。缓存的值只是优化手段,访问计数器则是关于使用情况的元数据。
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};谨慎使用 mutable,它只应应用于不会影响对象可观察状态的成员。
挑战
简单让我们构建一个文档查看器,用来跟踪文档被查看的次数,同时不破坏只读方法的 const 正确性。这是使用 mutable 关键字的绝佳场景。
你将创建两个文件来组织代码:
Document.h:定义一个Document类,用于存储内容并悄悄跟踪查看统计信息。你的类需要包含:- 私有成员:
title(字符串)、content(字符串),以及一个即使在 const 方法中也能修改的mutable int viewCount - 一个接收标题和内容的构造函数,并将
viewCount初始化为0 - 一个返回标题的
getTitle()const 方法 - 一个
getContent()const 方法,用于递增viewCount(因为它是 mutable,所以可以这样做)并返回内容 - 一个
getViewCount()const 方法,用于返回内容被访问的次数 - 一个
getPreview()const 方法,用于返回内容的前 20 个字符,后面接"..."。这不应该递增查看次数,因为这只是预览
- 私有成员:
main.cpp:演示 mutable 如何在保持方法逻辑上为 const 的同时跟踪访问统计信息。从输入中读取文档标题和内容(各占一行),然后:- 使用输入的值创建一个
Document - 打印
"Title: <title>" - 打印
"Preview: <preview>" - 打印
"Views after preview: <count>" - 使用
getContent()访问完整内容,并打印"Content: <content>" - 打印
"Views after first read: <count>" - 再次访问内容,并打印
"Content: <content>" - 打印
"Views after second read: <count>" - 创建一个辅助函数
void displayDocument(const Document& doc),用于打印"[Const Access] <title>: <content>"。这可以证明我们的 const 方法能够与 const 引用配合使用 - 使用你的文档调用
displayDocument() - 打印
"Final view count: <count>"
- 使用输入的值创建一个
预览后,查看次数应该是 0(因为预览不算完整查看),之后每次调用 getContent() 时都会递增。mutable 关键字让这一点成为可能。如果没有它,你就无法在 getContent() 这样的 const 方法中修改 viewCount。
如果内容不足 20 个字符,getPreview() 应该返回完整内容,后面接 "..."。
亲手试试
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: 实现 displayDocument 函数
// 它应输出:"[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: 实现这个函数
}
int main() {
// 读取输入
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: 用输入的值创建一个 Document 对象
// TODO: 输出 "Title: <title>"
// TODO: 输出 "Preview: <preview>"
// TODO: 输出 "Views after preview: <count>"
// TODO: 访问完整内容并输出 "Content: <content>"
// TODO: 输出 "Views after first read: <count>"
// TODO: 再次访问内容并输出 "Content: <content>"
// TODO: 输出 "Views after second read: <count>"
// TODO: 用你创建的文档调用 displayDocument()
// TODO: 输出 "Final view count: <count>"
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器