Menu
Coddy logo textTech

mutable キーワード

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 30/104。

const メンバ関数はメンバ変数を変更できないことを学びました。しかし、キャッシュやアクセス・カウンタのように、オブジェクトの論理的な状態に影響を与えないメンバを変更する必要がある場合があります。mutable キーワードは、この const 制約に対する例外を提供します。

mutable メンバは、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 がなければ、const 関数 getData() 内で accessCount をインクリメントするとコンパイルエラーが発生します。mutable キーワードはコンパイラに「このメンバは、オブジェクトが const と見なされている場合でも変更が許可される」と伝えます。

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 は控えめに使用してください。これは、オブジェクトの観察可能な状態に影響を与えないメンバに対してのみ適用されるべきです。

challenge icon

チャレンジ

簡単

読み取り専用メソッドの const 性を損なうことなく、ドキュメントが何回閲覧されたかを追跡するドキュメントビューアを作成しましょう。これは mutable キーワードの完璧なユースケースです。

コードを整理するために 2 つのファイルを作成します:

  • Document.h: コンテンツを保存し、閲覧統計を密かに追跡する Document クラスを定義します。クラスには以下が必要です:
    • プライベートメンバ: title (string)、content (string)、および const メソッド内でも変更可能な mutable int viewCount
    • タイトルとコンテンツを受け取り、viewCount0 に初期化するコンストラクタ
    • タイトルを返す getTitle() const メソッド
    • viewCount をインクリメントし(mutable なので許可されます)、コンテンツを返す getContent() const メソッド
    • コンテンツが何回アクセスされたかを返す getViewCount() const メソッド
    • コンテンツの最初の 20 文字の後に "..." を付けて返す getPreview() const メソッド。これはプレビューに過ぎないため、閲覧数をインクリメントしてはいけません。
  • main.cpp: メソッドを論理的に const に保ちながら、mutable がどのようにアクセス統計の追跡を可能にするかを実演します。入力からドキュメントのタイトルとコンテンツを(それぞれ別の行で)読み取り、以下の操作を行います:
    • 入力値を使用して Document を作成します
    • "Title: <title>" を出力します
    • "Preview: <preview>" を出力します
    • "Views after preview: <count>" を出力します
    • getContent() を使用してフルコンテンツにアクセスし、"Content: <content>" を出力します
    • "Views after first read: <count>" を出力します
    • 再度コンテンツにアクセスし、"Content: <content>" を出力します
    • "Views after second read: <count>" を出力します
    • "[Const Access] <title>: <content>" を出力するヘルパー関数 void displayDocument(const Document& doc) を作成します。これにより、const メソッドが const 参照で動作することが証明されます。
    • 作成したドキュメントで displayDocument() を呼び出します
    • "Final view count: <count>" を出力します

閲覧数はプレビューの後では 0 である必要があり(プレビューはフル閲覧としてカウントされないため)、その後 getContent() が呼び出されるたびにインクリメントされる必要があります。mutable キーワードがこれを可能にします。これがないと、getContent() のような const メソッド内で viewCount を変更することはできません。

コンテンツが 20 文字より短い場合、getPreview() はコンテンツ全体に続けて "..." を返す必要があります。

チートシート

mutable キーワードを使用すると、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 がないと、const 関数内でメンバ変数を変更するとコンパイルエラーが発生します。このキーワードは、オブジェクトが const と見なされる場合でも、そのメンバの変更が許可されていることをコンパイラに伝えます。

一般的なユースケースには、計算値のキャッシュ、アクセス統計の追跡、同期プリミティブなどがあります。この変更は、オブジェクトが論理的に表す内容を変更すべきではありません。

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 の使用は控えめにし、オブジェクトの外部から観測可能な状態に影響を与えないメンバに対してのみ使用してください。

自分で試してみよう

#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: 作成した document を引数に displayDocument() を呼び出す
    
    // TODO: "Final view count: <count>" を出力する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン