私はそれを聞くconst
手段をスレッドセーフにしてC ++ 11。本当?
それはやや本当です...
これは、標準言語がスレッドセーフに関して言っていることです。
[1.10 / 4]
2つの式の評価は、一方がメモリロケーション(1.7)を変更し、もう一方が同じメモリロケーションにアクセスまたは変更すると競合します。
[1.10 / 21]異なるスレッドに2つの競合するアクションが含まれ、少なくとも一方がアトミックではなく、どちらも先に発生しない場合
、プログラムの実行にはデータ競合が含まれます。このようなデータ競合は、未定義の動作を引き起こします。
これは、データの競合が発生するのに十分な条件にすぎません。
- 指定されたものに対して同時に2つ以上のアクションが実行されています。そして
- それらの少なくとも1つは書き込みです。
標準ライブラリは、さらに少し行くと、その上に構築します:
[17.6.5.9/1]
このセクションでは、データ競合(1.10)を防ぐために実装が満たす必要のある要件を指定します。特に指定のない限り、すべての標準ライブラリ関数は各要件を満たします。実装は、以下に指定されている以外の場合にデータの競合を防ぐ可能性があります。
[17.6.5.9/3]
C ++標準ライブラリ関数は、などの関数の非 const引数を介してオブジェクトに直接または間接的にアクセスしない限り、現在のスレッド以外のスレッドがアクセスできるオブジェクト(1.10)を直接的または間接的に変更してはなりませんthis
。
簡単に言うと、const
オブジェクトに対する操作はスレッドセーフであると想定しています。これは、独自のタイプのオブジェクトに対する操作である限り、標準ライブラリはデータ競合を導入しないことを意味しconst
ます
- 完全に読み取りで構成されている(つまり、書き込みがない)。または
- 内部で書き込みを同期します。
この期待がいずれかのタイプに当てはまらない場合、標準ライブラリのコンポーネントと直接または間接的に一緒に使用すると、データ競合が発生する可能性があります。結論として、const
意味がスレッドセーフから標準ライブラリのビューのポイントを。これは単なるコントラクトであり、コンパイラーによって強制されないことに注意してください。違反すると、未定義の動作が発生し、自分自身で動作するようになります。存在するかどうかconst
は、コード生成には影響しません- 少なくともデータ競合に関して- は影響しません。
const
これは、Javaの同等の機能になるということsynchronized
ですか。
いいえ。どういたしまして...
長方形を表す次の過度に簡略化されたクラスを考えてみます。
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
メンバー関数は、 area
あるスレッドセーフ。そのconst
ためではなく、完全に読み取り操作で構成されているためです。関連する書き込みはなく、データの競合が発生するには少なくとも1つの書き込みが必要です。つまり、必要なarea
数のスレッドから呼び出すことができ、常に正しい結果が得られます。
これrect
がスレッドセーフであるという意味ではないことに注意してください。実際には、その簡単にはへの呼び出しがあればどのように確認しarea
たの呼び出しと同時に起こるようにset_size
与えられた上でrect
、その後、area
(文字化け値に偶数か)古い幅と新しい高さに基づいてその結果を計算してしまう可能性があり。
しかし、それは問題rect
ありません。結局のところ、スレッドセーフであるconst
とは予想されていません。一方、宣言されたオブジェクトは、書き込みが不可能であるためスレッドセーフになります(そして、最初に宣言されたものを検討すると、未定義の動作が発生します)。const rect
const_cast
const
では、それはどういう意味ですか?
議論のために、乗算演算は非常にコストがかかり、可能な場合は回避する方がよいと仮定しましょう。要求された場合にのみ領域を計算し、将来再び要求される場合に備えて領域をキャッシュできます。
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[この例では、あまりにも人工的なようであれば、あなたは精神的に置き換えることができますint
によって、非常に大きな動的に割り当てられた整数本質的に非でスレッドセーフと乗算は非常に高価であるため。]
の メンバー関数は area
、もはやれるスレッドセーフ、それは今の書き込みをしていないと内部で同期されていません。それって問題ですか?への呼び出しは、別のオブジェクトのコピーコンストラクターのarea
一部として発生する可能性があります。このようなコンストラクターは、標準コンテナーの操作によって呼び出された可能性があり、その時点で標準ライブラリは、この操作がデータ競合に関して読み取りとして動作すると想定しています。しかし、書き込みを行っています!
すぐに私たちが置かようrect
に標準コンテナ --directlyまたはindirectly--我々が入っている契約と標準ライブラリを。const
そのコントラクトを守りながら関数で書き込みを続けるには、それらの書き込みを内部で同期する必要があります。
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
area
関数をスレッドセーフにしましたが、rect
それでもスレッドセーフではないことに注意してください。呼び出しarea
呼び出しがするのと同時に起こっset_size
まだに割り当てているので、間違った値を計算することになるかもしれないwidth
し、height
ミューテックスで保護されていません。
スレッドセーフが 本当に必要な場合はrect
、同期プリミティブを使用して非スレッドセーフ を保護しますrect
。
キーワードが不足していませんか?
はい、そうです。彼らは初日からキーワードを使い果たしています。
出典:あなたは知らないconst
とmutable
- ハーブサッター