Menu
CoddyTech

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.

challenge icon

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ớp Document lư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ức const
    • Một hàm khởi tạo nhận tiêu đề và nội dung, khởi tạo viewCount thành 0
    • Một phương thức getTitle() có const, trả về tiêu đề
    • Một phương thức getContent() có const, tăng viewCount (đượ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
  • main.cpp: Minh họa cách mutable cho 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ất const về 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 Document bằ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ức const hoạt động với tham chiếu const
    • Gọi displayDocument() với tài liệu của bạn
    • In "Final view count: <count>"

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;
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C++ online