Từ khóa mutable
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 30 trên 104.
Bạn đã học rằng hàm thành viên const không thể sửa đổi các biến thành viên. Nhưng đôi khi bạn cần thay đổi một thành viên không ảnh hưởng đến trạng thái logic của đối tượng, chẳng hạn như bộ nhớ đệm hoặc bộ đếm lượt truy cập. Từ khóa mutable tạo ra ngoại lệ đối với giới hạn của const.
Một thành viên có thể thay đổi vẫn có thể được sửa đổi ngay cả bên trong một hàm thành viên const:
class DataFetcher {
std::string data;
mutable int accessCount; // Có thể thay đổi trong các hàm (function) const
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Được phép vì accessCount là mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Nếu không có mutable, việc tăng accessCount bên trong hàm const getData() sẽ gây lỗi biên dịch. Từ khóa mutable cho trình biên dịch biết: “Thành viên này có thể thay đổi ngay cả khi đối tượng được xem là const.”
Các trường hợp thường dùng từ khóa mutable gồm lưu vào bộ nhớ đệm các giá trị đã tính toán, theo dõi thống kê truy cập và dùng các cơ chế đồng bộ như mutex. Trong mỗi trường hợp, việc sửa đổi không làm thay đổi ý nghĩa logic của đối tượng: giá trị được lưu vào bộ nhớ đệm chỉ là một cách tối ưu hóa, còn bộ đếm truy cập là dữ liệu bổ sung về cách sử dụng.
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;
}
};Chỉ dùng mutable khi thật cần thiết. Bạn chỉ nên áp dụng nó cho các thành viên không ảnh hưởng đến trạng thái có thể quan sát của đối tượng.
Thử thách
DễHãy xây dựng một trình xem tài liệu theo dõi số lần tài liệu được xem mà không làm mất tính đúng đắn của const trong các phương thức chỉ đọc. Đây là trường hợp sử dụng hoàn hảo cho từ khóa (keyword) mutable.
Bạn sẽ tạo hai tệp để sắp xếp code:
Document.h: Định nghĩa lớpDocumentlưu nội dung và âm thầm theo dõi thống kê lượt xem. Lớp của bạn cần có:- Các thành viên riêng tư:
title(chuỗi),content(chuỗi) vàmutable int viewCount, có thể thay đổi ngay cả trong các phương thứcconst - Một hàm khởi tạo nhận tiêu đề và nội dung, khởi tạo
viewCountthành0 - Một phương thức
getTitle()cóconst, trả về tiêu đề - Một phương thức
getContent()cóconst, tăngviewCount(được phép vì biến này có thể thay đổi) rồi trả về nội dung - Một phương thức
getViewCount()cóconst, trả về số lần nội dung được truy cập - Một phương thức
getPreview()cóconst, trả về 20 ký tự đầu tiên của nội dung, theo sau là"...". Phương thức này KHÔNG nên tăng số lượt xem vì đây chỉ là bản xem trước
- Các thành viên riêng tư:
main.cpp: Minh họa cáchmutablecho phép theo dõi thống kê truy cập trong khi vẫn giữ các phương thức có tính chấtconstvề mặt logic. Đọc tiêu đề và nội dung tài liệu từ đầu vào (mỗi giá trị trên một dòng riêng), sau đó:- Tạo một
Documentbằng các giá trị đầu vào - In
"Title: <title>" - In
"Preview: <preview>" - In
"Views after preview: <count>" - Truy cập toàn bộ nội dung bằng
getContent()và in"Content: <content>" - In
"Views after first read: <count>" - Truy cập lại nội dung và in
"Content: <content>" - In
"Views after second read: <count>" - Tạo một hàm trợ giúp
void displayDocument(const Document& doc)để in"[Const Access] <title>: <content>". Điều này chứng minh các phương thứcconsthoạt động với tham chiếuconst - Gọi
displayDocument()với tài liệu của bạn - In
"Final view count: <count>"
- Tạo một
Số lượt xem phải là 0 sau khi xem trước (vì bản xem trước không được tính là lượt xem đầy đủ), sau đó tăng lên mỗi lần gọi getContent(). Từ khóa mutable giúp bạn làm được điều này. Nếu không có từ khóa này, bạn không thể sửa đổi viewCount bên trong các phương thức const như getContent().
Nếu nội dung ngắn hơn 20 ký tự, getPreview() phải trả về toàn bộ nội dung, theo sau là "...".
Tự làm thử
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: Viết hàm (function) displayDocument
// Hàm này cần in ra: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: Viết hàm này
}
int main() {
// Đọc đầu vào
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: Tạo một Document với các giá trị đầu vào
// TODO: In ra "Title: <title>"
// TODO: In ra "Preview: <preview>"
// TODO: In ra "Views after preview: <count>"
// TODO: Truy cập toàn bộ nội dung và in ra "Content: <content>"
// TODO: In ra "Views after first read: <count>"
// TODO: Truy cập nội dung lần nữa và in ra "Content: <content>"
// TODO: In ra "Views after second read: <count>"
// TODO: Gọi displayDocument() với tài liệu của bạn
// TODO: In ra "Final view count: <count>"
return 0;
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online