Herb Sutterの話を見て constとmutableがわからない の場合、ミューテックスを常にミュータブルとして定義する必要があるかどうか疑問に思います。はいの場合、同期されたすべてのコンテナに同じことが当てはまると思います(例:tbb::concurrent_queue
)?
背景:彼の講演では、const == mutable == thread-safe、およびstd::mutex
は定義ごとにスレッドセーフです。
話についての関連質問もあります Does constはC++ 11でスレッドセーフを意味します 。
編集:
ここ 、関連する質問(おそらく重複)を見つけました。ただし、C++ 11の前に尋ねられました。多分それは違いを生みます。
いいえ。ただし、ほとんどの場合はそうなります。
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
メンバー関数は、this
にconst 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;
};
さて、スレッドセーフなものcanがmutable
としてマークされていることを確認しました。
両方の見方を同時に考える必要があると思います。ハーブの新しい観点から、はい。これらはスレッドセーフであるため、関数の定数に拘束される必要はありません。しかし、それらがcanで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
は、counter
メンバーもconst
であることを意味し、const
オブジェクトの変更は未定義の動作です。
これが、mutable
の側で誤る必要がある理由です。const_cast
は、同じ保証を提供しません。 counter
がmutable
とマークされていれば、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
の古くて根本的な使用法に依存します。
私はその話を見ただけで、ハーブサッターが言っていることに完全に同意しません。
私が正しく理解していれば、彼の主張は次のとおりです。
[res.on.data.races]/3
は、標準ライブラリで使用される型に要件を課します。非constメンバー関数はスレッドセーフでなければなりません。
したがって、const
はスレッドセーフと同等です。
また、const
がスレッドセーフと同等である場合、mutable
は「信頼してください。この変数の非constメンバーもスレッドセーフです」と同等でなければなりません。
私の意見では、この議論の3つの部分すべてに欠陥があります(そして、2番目の部分には重大な欠陥があります)。
1
の問題は、[res.on.data.races]
が標準ライブラリで使用する型ではなく、標準ライブラリの型の要件を提供することです。とは言っても、[res.on.data.races]
を標準ライブラリで使用する型の要件も与えると解釈することは合理的ですが(完全に明確ではありません)、ライブラリの実装がconst
メンバー関数がオブジェクトを変更できた場合、const
参照を通じてオブジェクトを変更しないという要件。
2
のcritical問題は、それが真である一方で(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
を作成するべきではありません。
オブジェクトの外部から見える状態に影響を与えないメンバー変数のためにmutable
を予約する必要があります。一方、(そしてこれがHerb Sutterが彼の講演で行う重要なポイントです)、何らかの理由でis変更可能なメンバーがある場合、そのメンバーは内部で同期する必要がありますそうしないと、const
がスレッドセーフにならない可能性があります。これにより、標準で未定義の動作が発生します。図書館。
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
以外の操作を呼び出す必要があり、何も中断しないことを保証できます。繰り返します:
mutable
と宣言できます。C++ 98と同様に、最初の条件が課されます。これは、標準ライブラリを含む他のコードがconst
メソッドを呼び出す可能性があり、そのような呼び出しによる変更を誰も観察してはならないためです。 2番目の条件はそこにあり、これはC++ 11の新機能です。そのような呼び出しは非同期で実行できるためです。
受け入れられた回答は質問をカバーしますが、Sutterがそれ以降const == mutable == thread-safeであると誤って示唆したスライドを変更したことは言及する価値があります。スライドの変更につながったブログ投稿は、次の場所にあります。
TL:DR ConstとMutableはどちらもスレッドセーフを意味しますが、プログラムで変更できることと変更できないことに関しては異なる意味があります。