45

ハーブサッターの話「あなたはconstとmutableを知らない」を見た後、私は常にミューテックスをミュータブルとして定義する必要があるのだろうか?はいの場合、同期されたコンテナ(たとえば)にも同じことが当てはまると思いtbb::concurrent_queueますか?

いくつかの背景:彼の講演で、彼はconst == mutable ==スレッドセーフであり、std::mutex定義ごとにスレッドセーフであると述べました。

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

編集:

ここで、関連する質問(おそらく重複)を見つけました。ただし、C++11より前に質問されました。多分それは違いを生むでしょう。

4

4 に答える 4

42

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

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_evenis_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;
};

さて、スレッドセーフなものはとしてマークできるmutableことを私たちは知っています、あなたは尋ねます:それらはそうあるべきですか?

両方の見方を同時に考えなければならないと思います。ハーブの新しい観点からは、そうです。これらはスレッドセーフであるため、関数の定数に拘束される必要はありません。しかし、彼らがの制約から安全に免除されることができるconstという理由だけで、彼らがそうしなければならないという意味ではありません。私はまだ考慮する必要があります:そのメンバーを変更した場合、実装のエラーになりますか?もしそうなら、それはそうである必要はありません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ことを意味します。counterconstconst

mutableこれが、 :の側で誤りを犯すべき理由ですconst_cast。まったく同じ保証を与えるわけではありません。マークcounterされていたら、それはオブジェクトmutableではなかったでしょう。const

さて、mutable1つの場所で必要な場合は、どこでも必要です。必要がない場合は注意が必要です。確かに、これはすべてのスレッドセーフメンバーにマーク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ある種の内部(スレッドセーフ)同期オブジェクトも使用していると主張していると思います。私は同意します。エルゴの視点はほとんどの場合機能します。しかし、私がたまたまスレッドセーフなオブジェクトを持っていて、それをさらに別のメンバーとして扱う場合があります。この場合、の古くて基本的な使用法に頼りますconst

于 2013-01-03T04:34:15.493 に答える
10

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

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

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

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

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

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

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

重大な問題は、スレッドセーフを意味する必要2があることは真実ですが(受け入れる場合1)、スレッドセーフが意味することは真実ではないため、この2つは同等ではありません。それでも「論理的に不変」を意味しますが、それは「論理的に不変」の範囲がスレッドセーフを必要とするように拡大したということです。constconstconst

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

//`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あり、2重大な欠陥があるということです。


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

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

mutableオブジェクトの外部から見える状態に影響を与えないメンバー変数を予約する必要があります。一方(これはハーブサッターが彼の話で述べている重要なポイントです)、何らかの理由で変更可能なメンバーがある場合、そのメンバー内部的に同期されている必要があります。そうしconstないと、スレッドセーフを意味しないリスクがあります。これにより、標準ライブラリで未定義の動作が発生します。

于 2013-01-03T04:10:38.170 に答える
6

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

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

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

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

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

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

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

于 2013-01-03T11:18:49.507 に答える
3

受け入れられた答えは質問をカバーしていますが、Sutterがスライドを変更したため、const==可変==スレッドセーフであると誤って示唆されたことは言及する価値があります。そのスライドの変更につながるブログ投稿はここにあります:

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

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

于 2013-08-26T18:17:45.887 に答える