web-dev-qa-db-ja.com

'mutable'キーワードには、const関数によって変数を変更することを許可する以外の目的がありますか?

少し前に、クラスのメンバー変数をmutableキーワードでマークするコードに出くわしました。私が見ることができる限り、それは単にあなたがconstメソッドで変数を修正することを可能にします:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

これはこのキーワードの唯一の使用法ですか?それとも目に見える以上のものがありますか?私はそれ以来boost::mutexをミュータブルとしてマークし、スレッドセーフの理由でconst関数がそれをロックすることができるようにクラスでこのテクニックを使いました、しかし、正直に言うと、それはちょっとしたハックのように感じます。

494
Rob

それはビットごとのconstと論理的constの区別を可能にします。論理定数とは、ロックの例のように、オブジェクトがパブリックインターフェイスを介して表示されるように変化しない場合です。もう1つの例は、値が最初に要求されたときに値を計算し、その結果をキャッシュするクラスです。

C++ 11のmutableは、値によってキャプチャされたものが変更可能であることを示すためにラムダに使用できるため(デフォルトではありません):

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda
331
KeithB

mutableキーワードは、オブジェクトを覆うconstベールに穴を開ける方法です。オブジェクトへのconst参照またはポインターがある場合は、mutableとマークされているとき、どのようにマークされているかによって、そのオブジェクトを変更することはできませんexcept

const参照またはポインターを使用すると、次の制約があります。

  • 可視データメンバの読み取りアクセスのみ
  • constとしてマークされているメソッドのみを呼び出す許可。

mutable例外により、mutableとマークされたデータメンバーを記述または設定できるようになります。それが外部から見える唯一の違いです。

内部的に、あなたに見えるconstメソッドは、mutableとマークされたデータメンバーに書き込むこともできます。基本的に、const veilは包括的に貫通されます。 mutableconstの概念を破壊せず、有用な特別な場合にのみ使用されるようにすることは、APIデザイナー次第です。 mutableキーワードは、これらの特殊なケースの対象となるデータメンバーを明確にマークするので役立ちます。

実際には、コードベース全体でconstを強引に使用できます(基本的にconst "disease"でコードベースを "感染"させたい)。この世界では、ポインターと参照はconstであり、例外はほとんどなく、推論と理解が容易なコードを生成します。興味深い余談については、「参照の透明性」を参照してください。

mutableキーワードがないと、最終的にconst_castを使用して、許可されるさまざまな便利な特殊なケース(キャッシュ、参照カウント、デバッグデータなど)を処理する必要があります。残念ながら、const_castmutableよりもかなり破壊的です。APIclientconst保護を破壊するためです。彼が使用しているオブジェクト。さらに、広範囲のconst破壊を引き起こします。constポインターまたは参照をconst_castingすることで、制限のない書き込みおよびメソッド呼び出しで可視メンバーにアクセスできます。対照的に、mutableでは、API設計者がconst例外をきめ細かく制御する必要があり、通常、これらの例外はconstメソッドで非公開データで動作します。

(NBデータとメソッドvisibilityを数回参照します。パブリックまたはプライベートまたは保護としてマークされたメンバーについて話します。まったく異なるタイプのオブジェクト保護について説明します。 ここ 。)

128
Dan L

Boost :: mutexを使用することはまさにこのキーワードが意図されていることです。もう1つの用途は、アクセスを高速化するための内部結果キャッシュです。

基本的に、 'mutable'はオブジェクトの外部から見える状態に影響を与えないあらゆるクラス属性に適用されます。

問題のサンプルコードでは、done_の値が外部状態に影響を与える場合、mutableは不適切かもしれません。部。

75
Frank Szczerba

Mutableは特定の属性をconstメソッド内から変更可能としてマークするためのものです。それが唯一の目的です。 mutableを使用するのではなくデザインを変更すると、コードがおそらくよりクリーンで読みやすくなるため、使用する前に慎重に検討してください。

http://www.highprogrammer.com/alan/rants/mutable.html

それで、上記の狂気がミュータブルのためではないのなら、それは何のためのものですか?これは微妙なケースです:mutableはオブジェクトが論理的に一定であるケースのためですが、実際には変更する必要があります。これらのケースはごくわずかで、その間にありますが、存在します。

作者が与える例としては、キャッシングや一時的なデバッグ変数があります。

35
John Millikin

キャッシュのように内部の状態を隠しているような状況で役立ちます。例えば:

 class HashTable 
 {
 ... [
 public:
]文字列参照(文字列キー)const 
 {
 ] if(key == lastKey)
 lastValueを返す; 
 
文字列値= lookupInternal(key); 
 
 lastKey = key; [。 lastValue = value; 
 
戻り値; 
} 
 
 private:
可変文字列lastKey、lastValue; 
};

それから、const HashTableオブジェクトにそのlookup()メソッドを使用させ、内部キャッシュを変更することができます。

31
Adam Rosenfield

mutableは、そうでなければ定数関数内のデータを変更できるようにするとあなたが推測したように存在します。

その意図は、あなたがオブジェクトの内部状態に「何もしない」という関数を持っているかもしれないので、あなたは関数constをマークするが、本当にそのオブジェクトの状態に影響を与えない方法で変更する必要があるかもしれません正しい機能.

キーワードは、コンパイラに対するヒントとして機能することがあります。理論上のコンパイラは、読み取り専用とマークされた定数オブジェクト(グローバルなど)をメモリに配置できます。 mutableの存在は、これが行われるべきではないことを示唆しています。

可変データを宣言して使用する正当な理由は次のとおりです。

  • スレッドの安全性mutable boost::mutexを宣言することは完全に合理的です。
  • 統計。引数の一部または全部を指定して、関数の呼び出し回数を数えます。
  • メモ。コストのかかる答えを計算し、それを再計算するのではなく、将来の参考のために保存します。
8
Lloyd

ええ、それはそれがすることです。論理的にクラスの状態を変更しないメソッドによって変更されたメンバには、これを使用します。たとえば、キャッシュを実装してルックアップを高速化するためです。

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

constメソッドのみを使用する場合、呼び出し側はそれらがスレッドセーフであると見なす可能性があるため、並行性の問題は大きな問題です。そしてもちろん、mutableデータを変更してもオブジェクトの動作が大きく変わることはありません。たとえば、ディスクに書き込まれた変更がすぐに表示されることが予想される場合は、私が示した例に違反します。アプリ。

8
Shog9

Mututeは、クラス内に変数があり、そのクラス内でのみ使用される変数がある場合に使用されます(ミューテックスやロックなど)。この変数はクラスの動作を変更しませんが、クラス自体のスレッドセーフを実装するために必要です。したがって、 "mutable"がなければ、 "const"関数を持つことはできません。なぜなら、この変数は、外界で利用可能なすべての関数で変更する必要があるからです。そのため、メンバー変数をconst関数でも書き込み可能にするためにmutableが導入されました。

指定されたmutableは、安全で、メンバ変数がconstメンバ関数内で変更されることが予想されることをコンパイラと読者の両方に知らせます。

6
mkschreder

C++の多くのものと同じように、変更可能な、行きたくない怠惰なプログラマーにとってはハックになり得ますずっとさかのぼって、constであってはいけないものをnon-constとしてマークします。

4
JohnMcG

mutableは主にクラスの実装の詳細に使われます。クラスのユーザはそれについて知る必要はありません。そのため、彼は「あるべきだ」と考えるメソッドを「する」ことができます。ミューテックスをミュータブルにするあなたの例は、良い標準的な例です。

4
Greg Rogers

ユーザーに対して論理的にステートレスであり(したがってパブリッククラスのAPIには「const」ゲッターがあるはずですが)、基礎となるIMPLEMENTATION(.cpp内のコード)ではステートレスではない場合は、「可変」を使用します。

私が最も頻繁に使用するケースは、ステートレスな "plain old data"メンバーの遅延初期化です。つまり、狭いケースでは、そのようなメンバが構築(プロセッサ)または持ち運び(メモリ)するのに費用がかかり、オブジェクトの多くのユーザが決して要求しないことが理想的です。構築されたオブジェクトの90%がまったく構築する必要がないため、パフォーマンスを向上させるためにバックエンドで遅延構築を行う必要がありますが、それでもパブリック消費用に正しいステートレスAPIを提示する必要があります。

3
Zack Yezek

ミュータブルを使用する最良の例の1つは、ディープコピーです。コピーコンストラクタでは、引数としてconst &objを送ります。そのため、作成された新しいオブジェクトは定数型になります。この新しく作成されたconstオブジェクトのメンバを変更したい場合(ほとんど変更することはほとんどありませんが、変更することがあります)、mutableとして宣言する必要があります。

mutable記憶クラスはクラスの非静的非constデータメンバでのみ使用できます。クラスの可変データメンバは、constとして宣言されているオブジェクトの一部であっても変更できます。

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

上記の例では、メンバ変数xの値はconstとして宣言されているオブジェクトの一部ですが、変更することができます。これは、変数xが可変として宣言されているためです。しかし、メンバ変数yの値を変更しようとすると、コンパイラはエラーをスローします。

Mutableキーワードは、クラステストの目的でスタブを作成するときに非常に便利です。あなたはconst関数をスタブすることができます、そしてあなたがまだあなたのスタブに追加した(変わりやすい)カウンターまたはどんなテスト機能でも増やすことができます。これにより、スタブ化されたクラスのインタフェースはそのまま残ります。

1
Martin G

場合によっては(設計が不適切なイテレータのように)、クラスはカウントまたはその他の付随的な値を保持する必要がありますが、それはクラスの主要な「状態」には実際には影響しません。これはほとんどの場合、私がミュータブルを使っているところです。変更可能でなければ、あなたはあなたのデザインの全体的な制約を犠牲にすることを余儀なくされるでしょう。

私にとっても、それはハックのような気がします。ごくわずかな状況で役立ちます。

1
Joe Schneider

(他の答えで述べたように)古典的な例と私がこれまでに使ったmutableキーワードが見た唯一の状況は、キャッシュがクラスのデータメンバーとして実装されているのではなく、複雑なGetメソッドの結果をキャッシュするためです。メソッド内の静的変数として(いくつかの関数間で共有するため、またはわかりやすさのために).

一般に、mutableキーワードを使用する代わりに、通常はメソッド内の静的変数またはconst_castトリックを使用します。

もう一つの詳しい説明はここ にあります

1

Mutableはクラスのconstの意味をbitwise constからlogical constに変更します。

つまり、可変メンバーを持つクラスはビット単位のconstになり、実行可能ファイルの読み取り専用セクションには表示されなくなります。

さらに、constメンバー関数がconst_castを使用せずに可変メンバーを変更できるようにして、型チェックを変更します。

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

詳細については他の回答を参照してください。しかし、これは単に型安全のためではなく、コンパイル結果に影響を与えることを強調したいと思いました。

1
Kevin Cox

あなたがconst仮想関数をオーバーライドしていて、その関数の中であなたの子クラスメンバ変数を修正したいとき、mutableは役に立ちます。ほとんどの場合、基底クラスのインターフェースを変更したくないので、自分の可変メンバー変数を使用する必要があります。

1
Saurabh

まさしくそのキーワード 'mutable'は、実際には予約されたキーワードです。定数variableの値を変えるのに使われることが多いです。

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
0
Rajdeep Rathore