レガシーコードの一部のラッパーがあります。
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
このレガシーコードでは、オブジェクトを「複製」する関数はスレッドセーフではない(同じ最初の引数を呼び出す場合)ためconst
、ラッパーでマークされていません。私は現代のルールに従っていると思います:https : //herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
これduplicate
は、そうではない詳細を除いて、コピーコンストラクタを実装する良い方法のように見えconst
ます。したがって、これを直接行うことはできません。
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
それでは、この逆説的な状況から抜け出す方法は何でしょうか?
(legacy_duplicate
スレッドセーフではないが、オブジェクトが終了したときにオブジェクトを元の状態のままにしておくこともできます。C関数であるため、動作は文書化されているだけで、一貫性の概念はありません。)
私は多くの可能なシナリオを考えることができます:
(1) 1つの可能性は、通常のセマンティクスでコピーコンストラクターを実装する方法がないことです。(はい、オブジェクトを移動できますが、それは必要ありません。)
(2)一方、オブジェクトをコピーすることは、単純なタイプをコピーするとソースが半分変更された状態で見つかる可能性があるという意味で、本質的にスレッドセーフではありません。
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3)または、duplicate
constを宣言し、すべてのコンテキストでスレッドの安全性について嘘をつきます。(結局、レガシー関数は気にしないconst
ので、コンパイラーは文句を言うことさえありません。)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4)最後に、ロジックに従って、const以外の引数を取るコピーコンストラクタを作成できます。
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
これらのオブジェクトは通常はそうではないため、これは多くのコンテキストで機能することがわかりますconst
。
問題は、これが有効なルートか、それとも一般的なルートか?
それらに名前を付けることはできませんが、非constコピーコンストラクターを使用することで、直感的に多くの問題が発生することが予想されます。おそらく、この微妙さのために、それは値タイプとして適格ではありません。
(5)最後に、これはやり過ぎで、実行時のコストが非常に高くなる可能性がありますが、ミューテックスを追加できます。
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
しかし、これを強制されることは悲観化のように見え、クラスを大きくします。私はわかりません。私は現在(4)、(5)、または両方の組み合わせに傾いています。
- 編集
別のオプション:
(6)重複するメンバー関数の意味がないことをすべて忘れてlegacy_duplicate
、コンストラクターから呼び出すだけで、コピーコンストラクターはスレッドセーフではないことを宣言します。(必要に応じて、そのタイプの別のスレッドセーフバージョンを作成しますA_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
編集2
これは、レガシー機能が行うことの良いモデルになる可能性があります。入力に触れることによる呼び出しは、最初の引数で表される値に関してスレッドセーフではないことに注意してください。
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
legacy_duplicate
、2つの異なるスレッドから同じ最初の引数を使用して関数を呼び出すことはできません。
const
本当の意味がわからない人々の一人でしょう。:-)私がconst&
変更しない限り、私はコピーActor を取得することについて2度考えませんother
。私は常にスレッドセーフティを、カプセル化によって複数のスレッドからアクセスする必要があるものに追加するものと考えており、答えを本当に楽しみにしています。
L
新しいL
インスタンスを作成することによって変更される状態に含まれていない状態はありますか?そうでない場合、なぜこの操作はスレッドセーフではないと思いますか?