web-dev-qa-db-ja.com

C ++ 11では常にstd :: mutexを変更可能として宣言しますか?

Herb Sutterの話を見て constとmutableがわからない の場合、ミューテックスを常にミュータブルとして定義する必要があるかどうか疑問に思います。はいの場合、同期されたすべてのコンテナに同じことが当てはまると思います(例:tbb::concurrent_queue)?

背景:彼の講演では、const == mutable == thread-safe、およびstd::mutexは定義ごとにスレッドセーフです。

話についての関連質問もあります Does constはC++ 11でスレッドセーフを意味します

編集:

ここ 、関連する質問(おそらく重複)を見つけました。ただし、C++ 11の前に尋ねられました。多分それは違いを生みます。

38
Philipp Claßen

いいえ。ただし、ほとんどの場合はそうなります。

constを「スレッドセーフ」と見なし、mutableを「(すでに)スレッドセーフ」と見なすと便利ですが、constは、「この値を変更しない」という約束の概念と根本的に関連しています。それは常になります。

私には長い考えがあり、我慢してください。

私自身のプログラミングでは、constをどこにでも配置しています。値がある場合、変更したくない場合を除き、値を変更することは悪いことです。 const-objectを意図的に変更しようとすると、コンパイル時エラーが発生します(修正は簡単で、出荷可能な結果はありません!)。非constオブジェクトを誤って変更すると、ランタイムプログラミングエラー、コンパイル済みアプリケーションのバグ、頭痛の種が発生します。したがって、前の側で誤りを犯し、constを維持することをお勧めします。

例えば:

bool is_even(const unsigned x)
{
    return (x % 2) == 0;
}

bool is_prime(const unsigned x)
{
    return /* left as an exercise for the reader */;
} 

template <typename Iterator>
void print_special_numbers(const Iterator first, const Iterator last)
{
    for (auto iter = first; iter != last; ++iter)
    {
        const auto& x = *iter;
        const bool isEven = is_even(x);
        const bool isPrime = is_prime(x);

        if (isEven && isPrime)
            std::cout << "Special number! " << x << std::endl;
    }
}

is_evenおよびis_primeのパラメータータイプがconstとマークされているのはなぜですか?実装の観点から見ると、テストしている数値を変更するとエラーになります。なぜconst auto& x?私はその値を変更するつもりはないので、もしそうした場合、コンパイラーに私に怒鳴りつけたいからです。 isEvenおよびisPrimeと同じ:このテストの結果は変更されないはずなので、強制します。

もちろん、constメンバー関数は、thisconst T*という形式の型を与えるための単なる方法です。 「一部のメンバーを変更すると、実装でエラーになる」と書かれています。

mutableは「私を除いて」と言います。これが、「論理的にconst」の「古い」概念の起源です。彼が与えた一般的なユースケースを考えてみましょう:ミューテックスメンバー。 必要このミューテックスをロックしてプログラムが正しいことを確認するため、変更する必要があります。ただし、他のメンバーを変更するとエラーになるので、関数を非定数にしたくはありません。したがって、それをconstにして、ミューテックスをmutableとしてマークします。

これはスレッドセーフとは関係ありません。

新しい定義が上記の古い考えに取って代わるというのは、一歩遠すぎると思います。スレッドセーフという別の見方から補足するだけです。

Herbは、const関数がある場合、標準ライブラリで安全に使用できるように、スレッドセーフである必要があるとしています。これの当然の結果として、mutable関数から変更できるため、constとして実際にマークする必要があるのは、すでにスレッドセーフなメンバーだけです。

struct foo
{
    void act() const
    {
        mNotThreadSafe = "oh crap! const meant I would be thread-safe!";
    }

    mutable std::string mNotThreadSafe;
};

さて、スレッドセーフなものcanmutableとしてマークされていることを確認しました。

両方の見方を同時に考える必要があると思います。ハーブの新しい観点から、はい。これらはスレッドセーフであるため、関数の定数に拘束される必要はありません。しかし、それらがcanconstの制約から安全に免除されているからといって、そうである必要はありません。私はまだ検討する必要があります:そのメンバーを変更した場合、実装でエラーになりますか?その場合、mutableである必要はありません。

ここには細かさの問題があります。一部の関数は、mutableメンバーになる可能性があるメンバーを変更する必要がある場合がありますが、そうでない場合もあります。これは、一部の関数だけにフレンドのようなアクセスを許可したいようなものですが、クラス全体をフレンドにすることしかできません。 (これは言語設計の問題です。)

この場合、mutableの側でエラーを発生させる必要があります。

ハーブは、const_castの例を安全であると宣言したとき、ほんの少し緩く話しすぎました。検討してください:

struct foo
{
    void act() const
    {
        const_cast<unsigned&>(counter)++;
    }

    unsigned counter;
};

これは、fooオブジェクト自体がconstである場合を除いて、ほとんどの状況で安全です。

foo x;
x.act(); // okay

const foo y;
y.act(); // UB!

これは、SOの他の場所で説明されていますが、const fooは、counterメンバーもconstであることを意味し、constオブジェクトの変更は未定義の動作です。

これが、mutableの側で誤る必要がある理由です。const_castは、同じ保証を提供しません。 countermutableとマークされていれば、constオブジェクトではありません。

わかりましたので、1つの場所でmutableが必要な場合は、どこでも必要です。必要がない場合は注意が必要です。確かに、これはすべてのスレッドセーフメンバーをmutableとしてマークする必要があることを意味しますか?

いや、すべてのスレッドセーフメンバーが内部同期のために存在するわけではないためです。最も平凡な例は、ある種のラッパークラスです(常にベストプラクティスとは限りませんが、存在します)。

struct threadsafe_container_wrapper
{
    void missing_function_I_really_want()
    {
        container.do_this();
        container.do_that();
    }

    const_container_view other_missing_function_I_really_want() const
    {
        return container.const_view();
    }

    threadsafe_container container;
};

ここでは、threadsafe_containerをラップして、必要な別のメンバー関数を提供しています(実際には、フリー関数としてより優れています)。ここではmutableの必要はありません。古い観点からの正しさは完全に切り捨てられます:1つの関数でコンテナーを変更していますそして、私は言わないので大丈夫ですconstの省略) 、そして他のものではコンテナを変更していませんそして私がその約束を守っていることを確認してくださいmutableを省略しています)。

Herbは、mutableを使用するほとんどの場合について議論していると思います。ある種の内部(スレッドセーフ)同期オブジェクトも使用しているので、同意します。エルゴの視点はほとんどの場合有効です。しかし、私が単純にhappenしてスレッドセーフなオブジェクトを作成し、それをさらに別のメンバーとして扱う場合もあります。この場合、constの古くて根本的な使用法に依存します。

38
GManNickG

私はその話を見ただけで、ハーブサッターが言っていることに完全に同意しません。

私が正しく理解していれば、彼の主張は次のとおりです。

  1. [res.on.data.races]/3は、標準ライブラリで使用される型に要件を課します。非constメンバー関数はスレッドセーフでなければなりません。

  2. したがって、constはスレッドセーフと同等です。

  3. また、constがスレッドセーフと同等である場合、mutableは「信頼してください。この変数の非constメンバーもスレッドセーフです」と同等でなければなりません。

私の意見では、この議論の3つの部分すべてに欠陥があります(そして、2番目の部分には重大な欠陥があります)。

1の問題は、[res.on.data.races]が標準ライブラリで使用する型ではなく、標準ライブラリの型の要件を提供することです。とは言っても、[res.on.data.races]を標準ライブラリで使用する型の要件も与えると解釈することは合理的ですが(完全に明確ではありません)、ライブラリの実装がconstメンバー関数がオブジェクトを変更できた場合、const参照を通じてオブジェクトを変更しないという要件。

2critical問題は、それが真である一方で(1を受け入れる場合)constスレッドセーフを意味する必要があります。スレッドセーフがconstを意味するのはnot trueであるため、2つは同等ではありません。 constは依然として「論理的に不変」を意味し、「論理的に不変」の範囲がスレッドセーフを必要とするように拡張されただけです。

constとスレッドセーフを同等と見なすと、constのNice機能が失われます。つまり、値を変更できる場所を確認することで、コードについて簡単に推論できます。

//`a` is `const` because `const` and thread-safe are equivalent.
//Does this function modify a?
void foo(std::atomic<int> const& a);

さらに、[res.on.data.races]の関連セクションでは、「変更」について説明しています。これは、「スレッドセーフでない方法での変更」ではなく、「外部から観察可能な方法での変更」というより一般的な意味で合理的に解釈できます。

3の問題は、2がtrueであり、2に重大な欠陥がある場合にのみtrueになる可能性があることです。


したがって、これを質問に適用するには-いいえ、すべての内部同期オブジェクトmutableを作成するべきではありません。

C++ 11の場合、C++ 03と同様に、 `const`は「論理的に不変」を意味し、` mutable`は「変更できるが、変更は外部から観察できない」ことを意味します。唯一の違いは、C++ 11では、「論理的に不変」が「スレッドセーフ」を含むように拡張されていることです。

オブジェクトの外部から見える状態に影響を与えないメンバー変数のためにmutableを予約する必要があります。一方、(そしてこれがHerb Sutterが彼の講演で行う重要なポイントです)、何らかの理由でis変更可能なメンバーがある場合、そのメンバーは内部で同期する必要がありますそうしないと、constがスレッドセーフにならない可能性があります。これにより、標準で未定義の動作が発生します。図書館。

10
Mankarse

constの変更について話しましょう。

void somefunc(Foo&);
void somefunc(const Foo&);

C++ 03以前では、constバージョンは、非constバージョンと比較して、呼び出し元に追加の保証を提供します。これは、引数を変更しないことを約束します。ここで、変更とは、Fooの非constメンバー関数(割り当てなどを含む)を呼び出すこと、または非const引数を予期する関数に渡すこと、または公開された変更不可能なデータメンバーに対しても同じことを行います。 somefuncは、constに対するFoo操作に制限されます。そして、追加の保証は完全に一方的です。呼び出し元もFooプロバイダーも、constバージョンを呼び出すために特別なことを行う必要はありません。 const以外のバージョンを呼び出すことができるユーザーは、constバージョンを呼び出すこともできます。

C++ 11ではこれが変更されました。 constバージョンでも呼び出し元に同じ保証が提供されますが、現在は価格が付いています。 Fooのプロバイダーは、すべてのconst操作がスレッドセーフであることを確認する必要があります。あるいは、somefuncが標準ライブラリ関数である場合は、少なくともそうする必要があります。どうして?標準ライブラリmayは操作を並列化し、willは、追加の同期を行わずに、あらゆるものに対してconst操作を呼び出します。したがって、ユーザーは、この追加の同期が不要であることを確認する必要があります。もちろん、ほとんどのクラスには変更可能なメンバーがなく、ほとんどのconst操作はグローバルデータに触れないため、これはほとんどの場合問題ではありません。

では、mutableはどういう意味ですか?以前と同じです!つまり、このデータはconstではありませんが、実装の詳細であり、観測可能な動作に影響を与えないことを約束します。つまり、C++ 98でそうしなかったのと同じように、mutableの中ですべてをマークする必要はありません。したがって、データメンバーをマークする必要がある場合はmutable? C++ 98の場合と同様に、constメソッドからconst以外の操作を呼び出す必要があり、何も中断しないことを保証できます。繰り返します:

  • データメンバーの物理的な状態がオブジェクトの監視可能な状態に影響しない場合
  • andスレッドセーフです(内部同期)
  • 次に、(必要に応じて)先に進んで、mutableと宣言できます。

C++ 98と同様に、最初の条件が課されます。これは、標準ライブラリを含む他のコードがconstメソッドを呼び出す可能性があり、そのような呼び出しによる変更を誰も観察してはならないためです。 2番目の条件はそこにあり、これはC++ 11の新機能です。そのような呼び出しは非同期で実行できるためです。

6

受け入れられた回答は質問をカバーしますが、Sutterがそれ以降const == mutable == thread-safeであると誤って示唆したスライドを変更したことは言及する価値があります。スライドの変更につながったブログ投稿は、次の場所にあります。

C++ 11のConstについてSutterが間違ったこと

TL:DR ConstとMutableはどちらもスレッドセーフを意味しますが、プログラムで変更できることと変更できないことに関しては異なる意味があります。

3
BobbyA