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-correctness を損なうことなく、ドキュメントが何回閲覧されたかを追跡するドキュメントビューアーを作成しましょう。これは mutable キーワードの最適な使用例です。

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

  • Document.h: コンテンツを格納し、閲覧統計を密かに追跡する Document クラスを定義します。クラスには次の要素が必要です。
    • private メンバー: title(string)、content(string)、および const メソッド内でも変更できる mutable int viewCount
    • title と content を受け取り、viewCount0 に初期化するコンストラクター
    • title を返す getTitle() const メソッド
    • viewCount をインクリメントし(mutable なので許可されます)、content を返す getContent() const メソッド
    • content が何回アクセスされたかを返す getViewCount() const メソッド
    • content の最初の20文字に続けて "..." を返す getPreview() const メソッド。これは単なるプレビューなので、view count をインクリメントしてはいけません
  • main.cpp: メソッドを論理的に const のままにしつつ、mutable によってアクセス統計を追跡できることを示します。ドキュメントの title と content を input から読み込み(それぞれ別の行)、次の処理を行います。
    • input の値で Document を作成する
    • "Title: <title>" を出力する
    • "Preview: <preview>" を出力する
    • "Views after preview: <count>" を出力する
    • getContent() を使用して full content にアクセスし、"Content: <content>" を出力する
    • "Views after first read: <count>" を出力する
    • content に再度アクセスし、"Content: <content>" を出力する
    • "Views after second read: <count>" を出力する
    • "[Const Access] <title>: <content>" を出力するヘルパー関数 void displayDocument(const Document& doc) を作成する。これにより、const メソッドが const 参照で動作することを確認できます
    • document を引数として displayDocument() を呼び出す
    • "Final view count: <count>" を出力する

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

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

自分で試してみよう

#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: Print "Views after preview: <count>"
    
    // TODO: フルコンテンツにアクセスして "Content: <content>" を出力する
    
    // TODO: Print "Views after first read: <count>"
    
    // TODO: 再度コンテンツにアクセスして "Content: <content>" を出力する
    
    // TODO: Print "Views after second read: <count>"
    
    // TODO: ドキュメントで displayDocument() を呼び出す
    
    // TODO: Print "Final view count: <count>"
    
    return 0;
}
quiz icon腕試し

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

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

自分で練習してみよう: C++オンラインコンパイラ