เหตุใดฟังก์ชันที่ถูกลบ C ++ 11 จึงมีส่วนร่วมในการแก้ปัญหาโอเวอร์โหลด


92

ทำไม C ++ 11 ทำให้" deleted" ฟังก์ชั่นมีส่วนร่วมในการแก้ปัญหาเกิน ?
เหตุใดจึงมีประโยชน์ หรือกล่าวอีกนัยหนึ่งว่าเหตุใดจึงซ่อนไว้แทนที่จะถูกลบทั้งหมด


พร้อมกันคำถามC ++ 11 "ไม่สามารถเคลื่อนย้าย" ประเภท
Olaf Dietsche

ดูเหตุผลในข้อเสนอopen-std.org/jtc1/sc22/wg21/docs/papers/2007/n2346.htm#delete
Jonathan Wakely

คำตอบ:


118

จุดประสงค์ครึ่งหนึ่งของ= deleteไวยากรณ์คือเพื่อป้องกันไม่ให้ผู้คนเรียกใช้ฟังก์ชันบางอย่างด้วยพารามิเตอร์บางอย่าง ส่วนใหญ่เป็นการป้องกันการแปลงโดยนัยในสถานการณ์เฉพาะบางสถานการณ์ เพื่อที่จะห้ามไม่ให้มีการโอเวอร์โหลดโดยเฉพาะจะต้องมีส่วนร่วมในการแก้ปัญหาการโอเวอร์โหลด

คำตอบที่คุณอ้างถึงเป็นตัวอย่างที่สมบูรณ์แบบ:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

หากdeleteลบฟังก์ชันออกทั้งหมดจะทำให้= deleteไวยากรณ์เทียบเท่ากับสิ่งนี้:

struct onlydouble2 {
  onlydouble2(double);
};

คุณสามารถทำได้:

onlydouble2 val(20);

นี่คือ C ++ ที่ถูกกฎหมาย คอมไพเลอร์จะดูตัวสร้างทั้งหมด ไม่มีใครใช้ประเภทจำนวนเต็มโดยตรง แต่หนึ่งในนั้นสามารถรับมันได้หลังจากการเปลี่ยนใจเลื่อมใสโดยปริยาย มันจะเรียกอย่างนั้น

onlydouble val(20);

นี่ไม่ใช่ C ++ ตามกฎหมาย คอมไพเลอร์จะดูตัวสร้างทั้งหมดรวมถึงตัวdeleted ด้วย จะเห็นการจับคู่แบบตรงทั้งหมดผ่านstd::intmax_t(ซึ่งจะตรงกับจำนวนเต็มลิเทอรัลใด ๆ ก็ตาม) ดังนั้นคอมไพลเลอร์จะเลือกและออกข้อผิดพลาดทันทีเนื่องจากเลือกdeleteฟังก์ชัน d

= deleteหมายความว่า "ฉันห้ามสิ่งนี้" ไม่ใช่แค่ "สิ่งนี้ไม่มีอยู่จริง" มันเป็นคำสั่งที่แข็งแกร่งกว่ามาก

ฉันถามว่าทำไมมาตรฐาน C ++ พูดว่า = ลบหมายความว่า "ฉันห้ามสิ่งนี้" แทนที่จะเป็น "สิ่งนี้ไม่มีอยู่จริง"

เป็นเพราะเราไม่จำเป็นต้องใช้ไวยากรณ์พิเศษในการพูดว่า "สิ่งนี้ไม่มีอยู่จริง" เราเข้าใจสิ่งนี้โดยปริยายโดยการไม่ประกาศ "สิ่งนี้" ที่เป็นปัญหา "ฉันห้ามสิ่งนี้" แสดงถึงโครงสร้างที่ไม่สามารถทำได้หากไม่มีไวยากรณ์พิเศษ ดังนั้นเราจึงได้รับไวยากรณ์พิเศษที่จะพูดว่า "ฉันห้ามสิ่งนี้" ไม่ใช่อย่างอื่น

ฟังก์ชันเดียวที่คุณจะได้รับจากการมีไวยากรณ์ "สิ่งนี้ไม่มีอยู่จริง" อย่างชัดเจนคือการป้องกันไม่ให้ใครบางคนประกาศว่ามีอยู่ในภายหลัง และนั่นก็ไม่มีประโยชน์เพียงพอที่จะต้องใช้ไวยากรณ์ของตัวเอง

ไม่มีวิธีใดที่จะประกาศว่าไม่มีตัวสร้างการคัดลอกและการมีอยู่ของมันอาจทำให้เกิดความคลุมเครือที่ไร้สาระ

ตัวสร้างการคัดลอกเป็นฟังก์ชันสมาชิกพิเศษ ทุกชั้นเรียนจะมีตัวสร้างสำเนาเสมอ เช่นเดียวกับที่พวกเขามีตัวดำเนินการกำหนดสำเนาย้ายตัวสร้าง ฯลฯ เสมอ

มีฟังก์ชันเหล่านี้ คำถามคือจะเรียกพวกเขาว่าถูกกฎหมายหรือไม่ หากคุณพยายามบอกว่า= deleteนั่นหมายความว่าไม่มีอยู่จริงข้อกำหนดจะต้องอธิบายความหมายของฟังก์ชันที่ไม่มีอยู่ นี่ไม่ใช่แนวคิดที่สเปคจัดการ

หากคุณพยายามเรียกใช้ฟังก์ชันที่ยังไม่ได้ประกาศ / กำหนดคอมไพลเลอร์จะผิดพลาด แต่จะเกิดข้อผิดพลาดเนื่องจากตัวระบุที่ไม่ได้กำหนดไม่ใช่เนื่องจากข้อผิดพลาด "ไม่มีฟังก์ชัน" (แม้ว่าคอมไพเลอร์ของคุณจะรายงานในลักษณะนั้นก็ตาม) คอนสตรัคเตอร์ต่างๆล้วนถูกเรียกใช้ด้วยความละเอียดเกินพิกัดดังนั้น "การมีอยู่" ของมันจึงถูกจัดการในเรื่องนั้น

ในทุกกรณีจะมีฟังก์ชันที่ประกาศผ่านตัวระบุหรือตัวสร้าง / ตัวทำลาย (ประกาศผ่านตัวระบุเช่นกันเพียงแค่ตัวระบุประเภท) ตัวดำเนินการที่มากเกินไปจะซ่อนตัวระบุไว้ด้านหลังของน้ำตาลที่มีปฏิกิริยา แต่ก็ยังคงอยู่ที่นั่น

ข้อกำหนด C ++ ไม่สามารถจัดการกับแนวคิดของ "ฟังก์ชันที่ไม่มีอยู่จริง" สามารถจัดการกับการโอเวอร์โหลดที่ไม่ตรงกัน สามารถจัดการกับความคลุมเครือเกินพิกัด แต่มันไม่รู้เกี่ยวกับสิ่งที่ไม่มี ดังนั้นจึง= deleteถูกกำหนดในแง่ของ "ความพยายามที่จะเรียกสิ่งนี้ว่าล้มเหลว" ที่มีประโยชน์มากกว่าคำว่า "แกล้งทำเป็นว่าฉันไม่เคยเขียนบรรทัดนี้" ที่มีประโยชน์น้อยกว่า

และอีกครั้งอ่านส่วนแรกอีกครั้ง คุณไม่สามารถทำได้โดย "ไม่มีฟังก์ชัน" นั่นเป็นอีกสาเหตุหนึ่งที่ทำให้มันถูกกำหนดแบบนั้น: เนื่องจากหนึ่งในกรณีการใช้งานหลักของ= deleteไวยากรณ์คือการบังคับให้ผู้ใช้ใช้พารามิเตอร์บางประเภทเพื่อแคสต์อย่างชัดเจนและอื่น ๆ โดยทั่วไปเพื่อทำลายการแปลงประเภทโดยนัย

คำแนะนำของคุณจะไม่ทำเช่นนั้น


1
@ Mehrdad: หากคุณต้องการคำชี้แจงเพิ่มเติมว่าทำไม = ลบไม่ได้ผลตามที่คุณต้องการคุณจะต้องมีความชัดเจนมากขึ้นเกี่ยวกับความหมายที่แน่นอนที่คุณคิดว่า = ลบควรมี มันควรจะเป็น "แกล้งทำเป็นว่าฉันไม่เคยเขียนบรรทัดนี้หรือไม่" หรือควรเป็นอย่างอื่น?
Nicol Bolas

1
ไม่ฉันหมายความว่าฉันคาดว่า= deleteจะหมายถึง "ไม่มีสมาชิกคนนี้" ซึ่งหมายความว่าไม่สามารถมีส่วนร่วมในการแก้ไขปัญหาเกินพิกัดได้
user541686

6
@ Mehrdad: และนั่นก็กลับไปที่จุดเดิมของฉันซึ่งเป็นสาเหตุที่ฉันโพสต์: ถ้า= deleteหมายถึง "ไม่มีสมาชิกคนนี้" ตัวอย่างแรกที่ฉันโพสต์จะไม่สามารถป้องกันไม่ให้คนส่งจำนวนเต็มไปยังตัวonlydoubleสร้างของ เนื่องจากonlydoubleเกินนั้นจะถูกลบจะไม่อยู่ มันจะไม่เข้าร่วมในการแก้ปัญหาโอเวอร์โหลดดังนั้นจึงไม่ป้องกันไม่ให้คุณส่งผ่านจำนวนเต็ม ซึ่งเป็นครึ่งหนึ่งของจุดสำคัญของ= deleteไวยากรณ์: เพื่อให้สามารถพูดได้ว่า "คุณไม่สามารถส่ง X ไปยังฟังก์ชันนี้โดยปริยายได้"
Nicol Bolas

3
@ Mehrdad: ด้วยเหตุผลนั้นทำไมคุณถึงต้องการ=deleteเลย? ท้ายที่สุดเราสามารถพูดว่า "ไม่สามารถคัดลอกได้" โดยทำสิ่งเดียวกันทุกประการนั่นคือการประกาศตัวสร้างการคัดลอก / การกำหนดเป็นส่วนตัว นอกจากนี้โปรดทราบว่าการประกาศสิ่งที่เป็นส่วนตัวไม่ได้ทำให้ไม่สามารถเรียกใช้ได้ รหัสภายในชั้นเรียนยังคงสามารถเรียกมันได้ = deleteดังนั้นจึงไม่เหมือนกัน ไม่= deleteไวยากรณ์ช่วยให้เราสามารถทำบางสิ่งที่ไม่สะดวกและไม่สามารถเข้าใจได้มาก่อนด้วยวิธีที่ชัดเจนและสมเหตุสมผลกว่ามาก
Nicol Bolas

2
@ Mehrdad: เพราะสิ่งหลังเป็นไปได้ : มันเรียกว่า "อย่าประกาศ" แล้วมันจะไม่อยู่ ว่าทำไมเราต้องไวยากรณ์เพื่อซ่อนเกินมากกว่าเหยียดหยามส่วนตัว ... คุณจริงๆขอเหตุผลที่เราควรจะมีวิธีการอย่างชัดเจนสิ่งที่รัฐมากกว่าเหยียดหยามบางคุณลักษณะอื่น ๆ ที่จะได้รับผลเช่นเดียวกัน ? เห็นได้ชัดกว่าสำหรับทุกคนที่อ่านโค้ดว่าเกิดอะไรขึ้น ช่วยให้ผู้ใช้เข้าใจโค้ดได้ง่ายขึ้นและเขียนง่ายขึ้นพร้อมทั้งแก้ไขปัญหาในการแก้ปัญหา เราไม่ต้องการแลมดาสเช่นกัน
Nicol Bolas

10

C ++ Working Draft 2012-11-02 ไม่ได้ให้เหตุผลเบื้องหลังกฎนี้เป็นเพียงตัวอย่างบางส่วน

8.4.3 นิยามที่ถูกลบ [dcl.fct.def.delete]
...
3 [ ตัวอย่าง : หนึ่งสามารถบังคับใช้การกำหนดค่าเริ่มต้นที่ไม่ใช่ค่าเริ่มต้นและการกำหนดค่าเริ่มต้นแบบไม่รวมกับ

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- end example ]
[ ตัวอย่าง : หนึ่งสามารถป้องกันการใช้คลาสในนิพจน์ใหม่บางอย่างได้โดยใช้คำจำกัดความที่ถูกลบของตัวดำเนินการที่ผู้ใช้ประกาศใหม่สำหรับคลาสนั้น

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

- end example ]
[ ตัวอย่าง : หนึ่งสามารถทำให้คลาสไม่สามารถคัดลอกได้เช่นย้ายอย่างเดียวโดยใช้คำจำกัดความที่ถูกลบของตัวสร้างการคัดลอกและตัวดำเนินการกำหนดสำเนาจากนั้นให้คำจำกัดความเริ่มต้นของตัวสร้างการย้ายและตัวดำเนินการกำหนดย้าย

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

- ตัวอย่างตอนท้าย ]


4
เหตุผลดูเหมือนชัดเจนมากจากตัวอย่างคุณไม่คิดเหรอ?
Jesse Good
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.