Saya punya pembungkus untuk beberapa kode warisan.
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
};
Dalam kode lawas ini, fungsi yang "menduplikasi" suatu objek bukanlah thread aman (saat memanggil argumen pertama yang sama), oleh karena itu tidak ditandai const
dalam pembungkus. Saya kira mengikuti aturan modern: https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
Ini duplicate
terlihat seperti cara yang baik untuk mengimplementasikan copy constructor, kecuali untuk detail yang bukan const
. Karena itu saya tidak dapat melakukan ini secara langsung:
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;}
};
Jadi apa jalan keluar dari situasi paradoks ini?
(Katakan juga itu legacy_duplicate
bukan thread-safe tapi saya tahu meninggalkan objek dalam keadaan asli ketika keluar. Sebagai fungsi-C perilaku hanya didokumentasikan tetapi tidak memiliki konsep keteguhan.)
Saya bisa memikirkan banyak skenario yang mungkin:
(1) Satu kemungkinan adalah bahwa tidak ada cara untuk mengimplementasikan copy constructor dengan semantik yang biasa sama sekali. (Ya, saya bisa memindahkan objek dan bukan itu yang saya butuhkan.)
(2) Di sisi lain, menyalin suatu objek secara inheren non-thread-safe dalam arti bahwa menyalin tipe sederhana dapat menemukan sumber dalam keadaan setengah dimodifikasi, jadi saya bisa maju dan melakukan ini mungkin,
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) atau bahkan hanya menyatakan duplicate
const dan kebohongan tentang keamanan utas dalam semua konteks. (Setelah semua fungsi warisan tidak peduli const
sehingga kompiler tidak akan mengeluh.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4) Akhirnya, saya bisa mengikuti logika dan membuat copy-constructor yang membutuhkan argumen non-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;}
};
Ternyata ini bekerja dalam banyak konteks, karena objek ini biasanya tidak const
.
Pertanyaannya adalah, apakah ini rute yang valid atau umum?
Saya tidak dapat menyebutkan nama mereka, tetapi secara intuitif saya berharap banyak masalah di jalan memiliki konstruktor salinan non-const. Mungkin itu tidak akan memenuhi syarat sebagai tipe nilai karena kehalusan ini.
(5) Akhirnya, meskipun ini tampaknya berlebihan dan bisa memiliki biaya runtime yang curam, saya bisa menambahkan mutex:
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;
};
Tetapi dipaksa untuk melakukan ini tampak seperti pesimisasi dan membuat kelas lebih besar. Saya tidak yakin. Saat ini saya condong ke arah (4) , atau (5) atau kombinasi keduanya.
—— EDIT
Pilihan lain:
(6) Lupakan semua non-sense dari fungsi anggota duplikat dan cukup panggil legacy_duplicate
dari konstruktor dan nyatakan bahwa konstruktor salinan tidak aman utas. (Dan jika perlu, buat versión lain yang aman untuk jenis ini, A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
EDIT 2
Ini bisa menjadi model yang baik untuk apa fungsi legacy tidak. Perhatikan bahwa dengan menyentuh input, panggilan tidak aman untuk nilai yang diwakili oleh argumen pertama.
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
tidak dapat dipanggil dengan argumen pertama yang sama dari dua utas yang berbeda.
const
sebenarnya berarti. :-) Saya tidak akan berpikir dua kali untuk mengambil const&
ctor copy saya selama saya tidak memodifikasi other
. Saya selalu menganggap keamanan utas sebagai sesuatu yang ditambahkan di atas apa pun yang perlu diakses dari banyak utas, melalui enkapsulasi, dan saya benar-benar menantikan jawaban.
L
yang dimodifikasi dengan membuat yang baruL
instance ? Jika tidak, mengapa Anda percaya bahwa operasi ini tidak aman?