'mutable'キーワードには、変数をconst関数で変更できるようにする以外の目的がありますか?


527

しばらく前に、クラスのメンバー変数をmutableキーワードでマークするコードを見つけました。私が見る限り、constメソッド内の変数を変更できます。

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

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


2
質問ですが、何も変更しない場合、なぜ最初にミューテックスを使用する必要があるのですか?私はこれを理解したいだけです。
Misgevolution

@Misgevolutionあなたは何かを変更しています、あなたはconstを介して誰が/どのように変更を行うことができるかを制御しているだけです。本当にナイーブな例ですが、私が非constハンドルを友達にだけ与えると、敵はconstハンドルを取得することを想像してください。友達は変更できますが、敵は変更できません。
iheanyi

1
注:ここではキーワードを使用しての偉大な例ですmutablestackoverflow.com/questions/15999123/...
ガブリエルステープルズ

const(型の)オーバーライドに使用できるようにしたいので、これを行う必要はありません: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;デフォルトでconst-by-defaultが必要な場合、つまりmutable A a;(明示的な変更可能)とA a;(暗黙的なconst)。
alfC

回答:


351

これにより、ビット単位のconstと論理的なconstを区別できます。論理的なconstは、ロックの例のように、オブジェクトがパブリックインターフェイスから見える方法で変更されない場合です。別の例としては、最初に要求されたときに値を計算し、その結果をキャッシュするクラスがあります。

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

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

52
'mutable'はビット単位/論理的な定数にはまったく影響しません。C ++はビット単位のconst のみであり、 'mutable'キーワードを使用して、このチェックからメンバーを除外できます。C ++では、抽象化(SmartPtrなど)以外で「論理的」なconstを達成することはできません。
Richard Corden

111
@リチャード:あなたは要点を逃している。「論理的const」キーワードはありません。本当です。それは、オブジェクトの論理的監視可能状態を構成するものの理解に基づいて、プログラマーが変更可能にすることによってどのメンバーを除外すべきかを決定するために行う概念的な違いです。
トニーデルロイ、2011

6
@ajayはい、それがconstオブジェクトで変更できるように、メンバー変数を可変としてマークすることの要点です。
KeithB 2013年

6
なぜラムダでミュータブルが必要なのですか?変数を参照でキャプチャするだけで十分ではないでしょうか?
Giorgio

11
@Giorgio:違いはx、ラムダ内で変更されたものがラムダ内に残ることですx。つまり、ラムダ関数はの独自のコピーのみを変更できます。変更は外からは見えず、オリジナルxはまだ変更されていません。ラムダはファンクタクラスとして実装されていると考えてください。キャプチャされた変数はメンバー変数に対応します。
セバスチャンマッハ

138

mutableキーワードは突き刺すする方法ですconstあなたがオブジェクトの上に羽織るベールを。オブジェクトへのconst参照またはポインタがある場合、いつ、どのようにマークされているかを除いて、そのオブジェクトを変更することはできませんmutable

あなたとのconst参照やポインタあなたがに制限されています。

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

mutableあなたは今、マークされているデータメンバを書いたり、セットすることができますので、例外はそれを作りますmutable。これが、外部から見える唯一の違いです。

内部const的には、表示されているメソッドは、マークされているデータメンバーに書き込むこともできますmutable。基本的に、constベールは包括的にピアスされます。それがコンセプトをmutable破壊しないことを保証するのは完全にAPIデザイナー次第でありconst、有用な特別な場合にのみ使用されます。mutableキーワードは、これらの特別なケースの対象となっていること明確にマークデータメンバために役立ちます。

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

なけれmutableキーワードあなたは最終的に使用するように強制されますconst_cast、それは(キャッシング、参照カウント、デバッグデータ等)ことができる種々の有用な特殊なケースを処理します。残念ながら、API クライアントが使用しているオブジェクトの保護を強制const_cast的に破壊するmutableため、より破壊的です。さらに、広範囲にわたる破壊を引き起こします。constポインターまたは参照を使用すると、可視メンバーへの自由な書き込みおよびメソッド呼び出しアクセスが可能になります。対照的に、APIデザイナーは例外をきめ細かく制御する必要があり、通常これらの例外はプライベートデータで動作するメソッドに隠されています。constconstconst_castmutableconstconst

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


8
さらに、オブジェクトのconst_cast一部を変更するために使用すると、const未定義の動作が発生します。
ブライアン、

APIクライアントがオブジェクトのconst保護を破棄する必要があるため、同意しません。あなたが使用していた場合const_castでのメンバ変数の突然変異実装するconst方法を、キャストを行うには、クライアントを聞かないでしょう-あなたはそれを行うだろうメソッド内const_castINGのthis。基本的には、特定の呼び出しサイトで任意のメンバーのconstnessをバイパスできる一方mutableで、特定のメンバーの constを削除できますすべての呼び出しサイトで。後者は通常、典型的な使用(キャッシュ、統計)に必要なものですが、const_castがパターンに適合する場合もあります。
BeeOnRope 2017年

1
const_castパターンは、あなたが一時的にメンバーを変更したいときなど、いくつかのケースでは、より良いフィット感を行い、その後、(のようなかなりそれを復元しますboost::mutex)。最終状態は初期状態と同じなので、メソッドは論理的にconstですが、その一時的な変更を行います。const_castその方法では具体的にconstをキャストできるので便利です。突然変異が元に戻されても、mutableconst保護が削除されるため、適切ではありません。すべてのメソッドありません。 、「元に戻す」パターン。
BeeOnRope 2017年

2
読み取り専用メモリ(より一般的には、読み取り専用とマークされたメモリ)へのconst 定義オブジェクトの可能な配置と、これを可能にする関連標準言語により、時限爆弾が発生する可能性があります。このようなオブジェクトは読み取り専用メモリに配置できないため、このような問題はありません。const_castmutable
BeeOnRope 2017年

75

boost :: mutexでの使用は、まさにこのキーワードの目的です。別の用途は、アクセスを高速化するための内部結果キャッシュです。

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

質問のサンプルコードでは、done_の値が外部の状態に影響を与える場合、mutableは不適切な場合があります。これは...の内容によって異なります。部。


35

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

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

それで、上記の狂気がミュータブルの目的ではない場合、それは何のためですか?ここに微妙なケースがあります:可変はオブジェクトが論理的に一定であるが実際には変更する必要がある場合です。これらのケースはほとんどありませんが、存在しています。

著者の例には、キャッシングや一時的なデバッグ変数が含まれます。


2
このリンクは、ミュータブルが役立つシナリオの最良の例を提供していると思います。ほとんどがデバッグ専用に使用されているようです。(正しい使用法ごと)
enthusiasticgeek

の使用によりmutable、コードがより読みやすく、よりクリーンになります。次の例では、期待どおりにreadできますconst。`変更可能なm_mutex; コンテナm_container; void add(Item item){Lockguard lock(m_mutex); m_container.pushback(item); }アイテムread()const {Lockguard lock(m_mutex); m_container.first();を返します。} `
Th。ティーレマン

非常に人気のあるユースケースが1つあります。それは参照カウントです。
セヴァアレクセイエフ

33

キャッシュなどの内部状態が非表示になっている場合に役立ちます。例えば:

クラスHashTable
{
...
公衆:
    文字列検索(文字列キー)const
    {
        if(key == lastKey)
            lastValueを返します。

        文字列値= lookupInternal(key);

        lastKey = key;
        lastValue = value;

        戻り値;
    }

民間:
    可変文字列lastKey、lastValue;
};

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


9

mutable それ以外の場合は一定の関数でデータを変更できるように推測すると、実際に存在します。

その目的は、オブジェクトの内部状態に対して「何もしない」関数がある可能性があるため、その関数constにマークを付けることですが、実際には、その正しい状態に影響を与えない方法で一部のオブジェクトの状態を変更する必要がある場合があります機能性。

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

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

  • スレッドセーフ。を宣言することmutable boost::mutexは完全に合理的です。
  • 統計。引数の一部またはすべてを指定して、関数の呼び出し回数をカウントします。
  • メモ化。高価な答えを計算し、それを再計算するのではなく、後で参照できるように保存します。

2
ミュータブルが「ヒント」であるというコメントを除いて、良い答えです。これにより、コンパイラーがオブジェクトをROMに配置した場合、可変メンバーが可変にならない場合があります。mutableの動作は明確に定義されています。
Richard Corden

2
読み取り専用メモリにconstオブジェクトを配置することとは別に、コンパイラーは、たとえば、ループ外のconst関数呼び出しを最適化することもできます。それ以外の場合はconst関数内の可変統計カウンターは、より多くの呼び出しをカウントするためだけに最適化を防ぐ代わりに、そのような最適化を許可します(そして1つの呼び出しのみをカウントします)。
ハーゲンフォンアイツェン2014

@HagenvonEitzen-私はそれが間違っていると確信しています。コンパイラーは、副作用がないことを証明できない限り、ループから関数を引き上げることはできません。その証明には、通常、関数の実装を実際に検査し(多くの場合、インライン化された後)、依存することはありませんconst(そのような検査は、constまたはに関係なく成功または失敗しmutableます)。関数を単に宣言するだけconstでは十分ではありません。const関数は、グローバル変数や関数に渡される何かを変更するなどの副作用を自由に持つことができるため、その証明の有用な保証にはなりません。
BeeOnRope 2017年

現在、一部のコンパイラには、gccの_attribute __((const))や__attribute __((pure)) などの特別な拡張機能があります。これらの機能_doに影響を与えますがconst、C ++のキーワードに関連するのは接線のみです。
BeeOnRope

8

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

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データを変更しても、オブジェクトの動作が大幅に変わることはありません。たとえば、ディスクに書き込まれた変更がすぐにアプリに表示されることが予想される場合、私が示した例に違反する可能性があります。 。


6

ミュータブルは、クラス内でのみ使用される変数がクラス内にある場合に使用されます。この変数は、たとえばミューテックスやロックなどの信号を送るために使用されます。この変数はクラスの動作を変更しませんが、クラス自体のスレッドセーフを実装するために必要です。したがって、「可変」でないと、「const」関数を使用できません。この変数は、外部の世界で使用できるすべての関数で変更する必要があるためです。したがって、メンバー変数をconst関数でも書き込み可能にするために、ミュータブルが導入されました。

指定されたmutableは、安全であり、メンバー変数がconstメンバー関数内で変更される可能性があることを期待して、コンパイラーとリーダーの両方に通知します。


4

mutableは主にクラスの実装の詳細で使用されます。クラスのユーザーはそれについて知る必要はないので、メソッドはconstであることが「できるはず」であると考えています。mutexを変更可能にする例は、良い標準的な例です。


4

C ++での多くのことのように、それを使用することはハックではありませんが、変更可能要素は、ずっと前に戻り、constでないものをnon-constとしてマークすることを望まない怠惰なプログラマにとってはハックになる可能性があります。


3

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

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


2

Mutable constは、クラスのビット単位の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

(設計が不十分なイテレータのように)場合によっては、クラスはカウントやその他の付随的な値を保持する必要がありますが、これはクラスの主要な「状態」に実際には影響しません。これは、ほとんどの場合、使用されるmutableです。変更可能でないと、設計全体の一貫性を犠牲にしなければなりません。

ほとんどの場合、私にとってもハックのように感じます。ごく一部の状況で役立ちます。


1

古典的な例(他の回答で述べたとおり)とmutableこれまでに使用したキーワードを見た唯一の状況は、複雑な結果をキャッシュするためのものですGetメソッドので、キャッシュはクラスのデータメンバーとして実装され、クラスのデータメンバーとして実装されませんメソッド内の静的変数(いくつかの関数間での共有または単純な清潔さのため)。

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

別の詳細な説明はここにあります


1
可変メンバーの一般的な代替として静的メンバーを使用することは聞いたことがありません。そして、何かが変更されないことconst_castわかっている(または保証されている)場合(たとえば、Cライブラリに干渉する場合)、またはconstとして宣言されていないことがわかっている場合のみです。つまり、constキャストされたconst変数を変更すると、未定義の動作が発生します。
セバスチャンマッハ

1
@phresnel「静的変数」とは、メソッド内の静的自動変数を意味します(呼び出し間でとどまります)。そしてconst_castconstメソッドのクラスメンバーを変更するために使用できます。これは、私が参照したものです...
ダニエルヘルシュコビッチ

1
あなたが「一般的に」書いたので、それは私には明らかではありませんでした:)を介して変更することに関してはconst_cast、これはオブジェクトが宣言されていない場合にのみ許可されconstます。たとえばconst Frob f; f.something();void something() const { const_cast<int&>(m_foo) = 2;結果は未定義の動作になります。
セバスチャンマッハ

1

mustは、const仮想関数をオーバーライドしていて、その関数の子クラスメンバー変数を変更する場合に便利です。ほとんどの場合、基本クラスのインターフェースを変更したくないので、独自の可変メンバー変数を使用する必要があります。


1

mutableキーワードは、クラステストの目的でスタブを作成するときに非常に役立ちます。const関数をスタブしても、(可変)カウンターや、スタブに追加したテスト機能を増やすことができます。これにより、スタブ化されたクラスのインターフェースがそのまま維持されます。


0

mutableを使用する最良の例の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、コンパイラーはエラーをスローします。


-1

キーワード 'mutable'は実際には予約済みのキーワードです。多くの場合、定数変数の値を変更するために使用されます。定数の複数の値が必要な場合は、キーワードmutableを使用します。

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.