1

関数呼び出しパラメーターがどのようにインターリーブされるかについて正確に指定されていることを完全に理解したいと思います。私には多くの意味があるように思えます。次の例を見てください。

void mad(cow_string a, cow_string b);
cow_string s("moo");
cow_string s1 = s;
cow_string s2 = s;
mad(s1+="haha",s2+="hahaha");

Sutterがcow_stringGotW で説明しているような Copy-On-Write 文字列コンテナーはどこにありますか: http://www.gotw.ca/gotw/045.htm

  1. s1+="haha"との評価がs2+="hahaha"非常に細かい粒度にインターリーブされている場合、これは (コンパイラに応じて) cow_strings 内部参照カウントで競合状態が発生していることを意味しませんか?

  2. ミューテックスを使用して競合状態から保護しようとすると、シングルスレッドプログラムでセルフロックが発生することさえありませんでした (頭が痛くなります)。たとえば、S1 は内部コピーを作成し、mutex を取得して ref カウントコンテキスト スイッチを減らします。S2 も内部コピーを作成し、mutex と bam セルフ ロックを実行します。

  3. (最初が true の場合のみ) 私のチームの他のメンバーが専門家でない場合、またはそのオブジェクトが COW であることを知らない場合、オブジェクトを COW にする安全な方法はありますか?

編集:

明確にするために、式があまりインターリーブされていないという私の図は、次の Herb Sutters の例によって揺さぶられました。

// In some header file:
void f( T1*, T2* );

// In some implementation file:
f( new T1, new T2 );

これを行う:

allocate memory for the T1
construct the T1
allocate memory for the T2
construct the T2
call f()

またはこれ:

allocate memory for the T1
allocate memory for the T2
construct the T1
construct the T2
call f()

ここでそれについて読んでください:http://flylib.com/books/en/3.259.1.55/1/

cow_string2 番目の編集:参照カウンターを変更する関数がインライン化されると想定していたと思いますが、これはばかげた想定です。そのばかげた仮定がなければ、私の質問はあまり意味がありません。答えてくれてありがとう!

4

2 に答える 2

3

質問が次のように変更された場合:

void mad(cow_string & a, cow_string & b);
cow_string s("moo");
cow_string s1 = s;
cow_string s2 = s;
mad(s1+="haha",s2+="hahaha");

もう少し意味のある質問があります。ここで、 と の間の相互作用は、コンパイラが何らかの方法で実行をインターリーブした場合 (おそらく余分なスレッドを投入することによって) 干渉する可能性がs1 +=あります。s2 +=

しかし、いいえ、できません。C++ コンパイラは余分なスレッドをスローせず、メソッドを 1/2 実行してから別のメソッドの実行に切り替えることもありません。 s1またはは完了するまで実行されcow_string::operator+=、その後でのみ他方が開始され、両方が完了した後にのみ呼び出されます。s2cow_string::operator+=mad

への呼び出しでの部分式の実行順序はmadコンパイラの実装に任されていますが、単一のスレッドでインターリーブすることはできず、標準のコンパイラは余分なスレッドをスローすることはできません。

Herb Sutter は、部分式が左から右の順序、または深さ優先の順序で発生する必要がないという点を理解しようとしています。むしろ、関数呼び出し自体のルール フレームワーク内で任意の順序 (インターリーブを含む) で発生する可能性があります。

その最後の部分は重要です。基本的な呼び出しメカニズム、または完全な引数渡し期間の評価順序に違反することはできません。

したがって、上記の式に 4 つのミニ演算があると判断した場合:

A)は B)に渡される"haha"一時に変換されます) C) についても同じです) A から の一時は D) に渡されますcow_stringcow_string::operator+=
"hahaha"
S1::+=
S2::+=

これが下がる無限の方法はありません。

A, B, C, D
A, B, D, C
A, C, B, D
B, D, A, C
B, A, C, D
B, A, D, C

それでおしまい。などの関数呼び出しcow_string(const char*)はインターリーブできません。operator もそうではありません+=。それらは関数呼び出しです。それらの引数は、呼び出す前に完全に評価する必要があります。外部コンテキストでのさらなる評価が再開される前に、呼び出しが完全に完了する必要があります。

以下は、実際にはあいまいな例です。

int a = 5;
foo(a+=9*4, a+=13/2);

コンパイラは、引数 (および引数内の部分式) を foo に評価する順序を任意の順序で選択できます。したがって、foo()それを受け取ったときに a がどうなるかは誰の推測でもあります (コンパイラによって異なります)。


関数への引数としての new への2つの呼び出しの編集例について。

foo(new T1, new T2);

いずれかまたは両方newの s がいずれかのコンストラクターの前に呼び出される可能性があり、スローされる可能性があるため、メモリ リークが発生する可能性があります。

コンパイラが生成する場合:

new T1  
new T2  
T1()  
T2()  

new T2スローされると、のメモリT1が失われます。割り当てを解除するスペースの所有者はいませんT1

コンパイラがnew T1, T1(), throw を呼び出したとしても、占有new T2するスペースを誰も所有していないため、ここでメモリ リークが発生する可能性があります。したがって、生成された副作用は、元に戻す/管理する/クリーンアップする/などは行われません。T1T1

ハーブサッターを読み続けてください。彼の Exceptional C++ と More Exceptional C++ は優れており、これらの問題を深く掘り下げています。

于 2011-12-08T14:41:46.613 に答える
1

あなたの質問が何であるかわかりません。への呼び出しには文字列のmad書き込みがないため、書き込み時のコピーは機能しません。唯一のコピーは、+演算子の一時的な結果と値パラメーターからのものですmad(これらは省略できます)。

スレッド化に関しては、スレッド化の問題がコピー オン ライトが人気を失った理由の 1 つです。g++ ではまだ使用されていますが、スレッド処理にエラーがあります (非常に例外的な状況でのみ発生する可能性があります)。一般的に言えば、書き込み時にスレッドセーフなコピーを作成することは難しくなく、書き込み時に効率的なコピーを作成することも難しくありませんが、この 2 つを組み合わせることはほとんど不可能です。(少なくとも のインターフェイスについてはstd::basic_string<>。より合理的なインターフェイスがあれば、それほど難しくありません。)

スレッド セーフなコピー オン ライトの主な問題は、使用カウントの更新をアトミックにstd::basic_stringすることです。外部からの変更が他のインスタンスに影響を与えないように、常に 1 のカウントを使用します) は、文字列が分離されているというマークが付いたアトミックです。(この最後のポイントは、g++ 実装が失敗する場所です。あるスレッドで文字列をコピーし、別のスレッドで文字列にアクセスしようとするoperator[]と、最初の使用カウントが 1 で、実装のコピーを共有する 2 つのインスタンスになる可能性がありますこれは孤立しているとマークされています — C++ ソース コードのコメントでは、これを &ldqou;orphaned と呼んでいます)。

とにかく、あなたがcow_string示すコード を考えるss1: 1 の、最初)。しかし、あなたの問題が何であるかはわかりません.コードは文字列を変更しないため、唯一の問題は、使用カウントの更新がアトミックであることを確認することです.s2s1 + "haha"s2 + "hahaha"

于 2011-12-08T14:30:44.970 に答える