共有ポインターを解放するコードを想像してください。
auto tmp = &(the_ptr->a);
*tmp = 10;
the_ptr.dec_ref();
dec_ref() に「リリース」セマンティックがない場合、コンパイラ (または CPU) が dec_ref() の前から後に物事を移動することはまったく問題ありません (たとえば)。
auto tmp = &(the_ptr->a);
the_ptr.dec_ref();
*tmp = 10;
dec_ref() は他のスレッドから同時に呼び出され、オブジェクトを削除する可能性があるため、これは安全ではありません。したがって、dec_ref() がそこにとどまる前に、「リリース」セマンティックが必要です。
オブジェクトのデストラクタが次のようになっていると想像してみましょう。
~object() {
auto xxx = a;
printf("%i\n", xxx);
}
また、例を少し変更して、2 つのスレッドを作成します。
// thread 1
auto tmp = &(the_ptr->a);
*tmp = 10;
the_ptr.dec_ref();
// thread 2
the_ptr.dec_ref();
次に、「集約された」コードは次のようになります。
// thread 1
auto tmp = &(the_ptr->a);
*tmp = 10;
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
auto xxx = a;
printf("%i\n", xxx);
}
}
}
// thread 2
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
auto xxx = a;
printf("%i\n", xxx);
}
}
}
ただし、atomic_sub() の「リリース」セマンティックしかない場合、このコードは次のように最適化できます。
// thread 2
auto xxx = the_ptr->a; // "auto xxx = a;" from destructor moved here
{ // the_ptr.dec_ref();
if (0 == atomic_sub(...)) {
{ //~object()
printf("%i\n", xxx);
}
}
}
しかし、そのようにすれば、デストラクタは常に "a" の最後の値を出力するとは限りません (このコードはもうレースフリーではありません)。そのため、atomic_sub のセマンティックを取得する必要もあります (厳密には、デクリメント後にカウンターが 0 になったときにバリアを取得する必要があります)。