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 は控えめに使用してください。これは、オブジェクトの観察可能な状態に影響を与えないメンバに対してのみ適用されるべきです。
チャレンジ
簡単読み取り専用メソッドの const 性を損なうことなく、ドキュメントが何回閲覧されたかを追跡するドキュメントビューアを作成しましょう。これは mutable キーワードの完璧なユースケースです。
コードを整理するために 2 つのファイルを作成します:
Document.h: コンテンツを保存し、閲覧統計を密かに追跡するDocumentクラスを定義します。クラスには以下が必要です:- プライベートメンバ:
title(string)、content(string)、および const メソッド内でも変更可能なmutable int viewCount - タイトルとコンテンツを受け取り、
viewCountを0に初期化するコンストラクタ - タイトルを返す
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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス