ฉันมีเสื้อคลุมสำหรับรหัสดั้งเดิมบางส่วน
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
ใน wrapper ฉันเดากฎต่อไปนี้ที่ทันสมัย: 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-function พฤติกรรมจะได้รับการบันทึกไว้เท่านั้น แต่ไม่มีแนวคิดเรื่องความมั่นคง)
ฉันสามารถคิดถึงสถานการณ์ที่เป็นไปได้มากมาย:
(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
ดังนั้นคอมไพเลอร์จะไม่บ่น)
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
บริบทเพราะวัตถุเหล่านี้มักจะไม่
คำถามคือมันเป็นเส้นทางที่ถูกต้องหรือทั่วไป?
ฉันไม่สามารถตั้งชื่อพวกเขาได้ แต่ฉันคาดหวังว่าจะมีปัญหามากมายตามทางที่จะมีคอนสตรัคเตอร์ที่ไม่ใช่แบบคอน อาจเป็นไปได้ว่ามันจะไม่ถือว่าเป็นประเภทค่าเนื่องจากความละเอียดนี้
(5)ในที่สุดแม้ว่าสิ่งนี้จะเกินความจริงและอาจมีค่าใช้จ่ายสูงชัน แต่ฉันสามารถเพิ่ม 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;
};
แต่การถูกบังคับให้ทำสิ่งนี้ดูเหมือนว่าจะทำให้เป็นโมฆะและทำให้ชั้นเรียนใหญ่ขึ้น ฉันไม่แน่ใจ. ฉันกำลังโน้มตัวไปยัง(4)หรือ(5)หรือทั้งสองอย่างรวมกัน
แก้ไข 1:
ตัวเลือกอื่น:
(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;
}
แก้ไข 3:
ฉันได้เรียนรู้เมื่อเร็ว ๆ นี้ว่าstd::auto_ptr
มีปัญหาคล้ายกันในการมีตัวสร้าง "คัดลอก" ที่ไม่ใช่ const ผลคือauto_ptr
ไม่สามารถใช้ภายในคอนเทนเนอร์ได้ https://www.quantstart.com/articles/STL-Containers-and-Auto_ptrs-Why-They-Dont-Mix/
legacy_duplicate
ไม่สามารถเรียกด้วยอาร์กิวเมนต์แรกเดียวกันจากสองเธรดที่แตกต่างกัน
const
จริง ๆ แล้วหมายถึงอะไร :-) ฉันไม่อยากจะคิดว่าสองครั้งเกี่ยวกับการconst&
ใน ctor other
สำเนาของฉันตราบเท่าที่ฉันไม่แก้ไข ฉันมักจะนึกถึงความปลอดภัยของเธรดเป็นสิ่งที่เราจะต้องเข้าถึงได้จากหลาย ๆ หัวข้อผ่านการห่อหุ้มและฉันหวังว่าจะได้คำตอบจริงๆ
L
ซึ่งถูกแก้ไขโดยการสร้างL
อินสแตนซ์ใหม่หรือไม่? หากไม่เป็นเช่นนั้นคุณเชื่อว่าการดำเนินการนี้ไม่ปลอดภัยสำหรับเธรดหรือไม่