คำตอบส่วนใหญ่ที่นี่ไม่สามารถแก้ปัญหาความกำกวมโดยธรรมชาติในการมีตัวชี้แบบดิบในลายเซ็นฟังก์ชันในแง่ของการแสดงเจตนา ปัญหาดังต่อไปนี้:
ผู้เรียกไม่ทราบว่าตัวชี้ชี้ไปที่วัตถุเดียวหรือไปยังจุดเริ่มต้นของ "อาร์เรย์" ของวัตถุ
ผู้เรียกไม่ทราบว่าตัวชี้ "เป็นเจ้าของ" หน่วยความจำที่ชี้ไป IE ไม่ว่าจะเป็นฟังก์ชั่นควรเพิ่มหน่วยความจำหรือไม่ ( foo(new int)
- นี่เป็นหน่วยความจำรั่วหรือไม่)
ผู้เรียกไม่ทราบว่าnullptr
สามารถส่งผ่านไปยังฟังก์ชันได้อย่างปลอดภัยหรือไม่
ปัญหาทั้งหมดเหล่านี้ได้รับการแก้ไขโดยการอ้างอิง:
การอ้างอิงอ้างถึงวัตถุเดียวเสมอ
การอ้างอิงไม่เคยเป็นเจ้าของหน่วยความจำที่อ้างถึง แต่เป็นเพียงมุมมองในหน่วยความจำ
ข้อมูลอ้างอิงต้องไม่เป็นค่าว่าง
สิ่งนี้ทำให้การอ้างอิงเป็นตัวเลือกที่ดีกว่าสำหรับการใช้งานทั่วไป อย่างไรก็ตามการอ้างอิงไม่สมบูรณ์ - มีปัญหาใหญ่สองสามข้อที่ต้องพิจารณา
- ไม่มีทางอ้อมอย่างชัดเจน นี่ไม่ใช่ปัญหากับตัวชี้แบบดิบเนื่องจากเราต้องใช้ตัว
&
ดำเนินการเพื่อแสดงว่าเรากำลังผ่านตัวชี้ ตัวอย่างเช่น,int a = 5; foo(a);
มันไม่ชัดเจนเลยที่นี่ว่า a กำลังถูกส่งผ่านโดยการอ้างอิงและสามารถแก้ไขได้
- Nullability จุดอ่อนของพอยน์เตอร์นี้สามารถเป็นจุดแข็งได้เมื่อเราต้องการให้การอ้างอิงของเราเป็นโมฆะ เมื่อเห็นว่า
std::optional<T&>
ไม่ถูกต้อง (สำหรับเหตุผลที่ดี) พอยน์เตอร์จะบอกเราว่าคุณต้องการความถูกต้อง
ดังนั้นดูเหมือนว่าเมื่อเราต้องการการอ้างอิงที่เป็นโมฆะพร้อมกับการบอกทางอย่างชัดแจ้งเราควรไปถึง T*
ใช่ไหม? ไม่ถูกต้อง!
นามธรรม
ในความสิ้นหวังของเราสำหรับความไร้ผลเราอาจถึงT*
และเพียงเพิกเฉยต่อข้อบกพร่องและความกำกวมเชิงความหมายที่ระบุไว้ก่อนหน้า แต่เราควรไปถึงสิ่งที่ C ++ ทำได้ดีที่สุด: สิ่งที่เป็นนามธรรม ถ้าเราเพียงแค่เขียนคลาสที่ล้อมรอบพอยน์เตอร์เราจะได้รับการแสดงออกเช่นเดียวกับความไร้ประสิทธิภาพและความไม่แน่นอนทางอ้อม
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
นี่เป็นอินเทอร์เฟซที่ง่ายที่สุดที่ฉันสามารถทำได้ แต่ทำงานได้อย่างมีประสิทธิภาพ จะช่วยให้การเริ่มต้นการอ้างอิงตรวจสอบว่ามีค่าอยู่และเข้าถึงค่า เราสามารถใช้มันได้เช่น:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
เราบรรลุเป้าหมายของเราแล้ว! ตอนนี้ให้เราเห็นประโยชน์เมื่อเทียบกับตัวชี้ดิบ
- อินเทอร์เฟซแสดงอย่างชัดเจนว่าการอ้างอิงควรอ้างถึงวัตถุเดียวเท่านั้น
- เห็นได้ชัดว่ามันไม่ได้เป็นเจ้าของหน่วยความจำมันหมายถึงเนื่องจากมันไม่มี destructor ที่ผู้ใช้กำหนดและไม่มีวิธีการลบหน่วยความจำ
- ผู้โทรรู้
nullptr
สามารถส่งต่อได้เนื่องจากผู้เขียนฟังก์ชั่นร้องขออย่างชัดเจนoptional_ref
เราสามารถทำให้ส่วนต่อประสานที่ซับซ้อนมากขึ้นจากที่นี่เช่นการเพิ่มตัวดำเนินการความเท่าเทียมกัน monadic get_or
และmap
ส่วนต่อประสานซึ่งเป็นวิธีการที่ได้รับคุณค่าหรือส่งข้อยกเว้นconstexpr
สนับสนุน ที่สามารถทำได้โดยคุณ
โดยสรุปแทนที่จะใช้พอยน์เตอร์แบบดิบให้เหตุผลเกี่ยวกับความหมายของพอยน์เตอร์ในรหัสของคุณและใช้ประโยชน์จากไลบรารี่มาตรฐานที่เป็นนามธรรมหรือเขียนของคุณเอง สิ่งนี้จะปรับปรุงรหัสของคุณอย่างมีนัยสำคัญ
new
การสร้างตัวชี้และปัญหาที่เกิดจากการเป็นเจ้าของ