ข้าม​ไป​ยัง​เนื้อหา

คีย์เวิร์ด​เดียว​ที่​หาย​ไป เปลี่ยน​การ​คัด​ลอก​จาก​ศูนย์​เป็น​หมื่น​หก โดย​ไม่มี​สวิตช์​ไหน​เตือน

คุณ​เขียน class ที่​มี move constructor ของ​ตัว​เอง แล้ว​เอา​ไป​ใส่ std::vector

class นั้น​ย้าย​ได้ · vector รู้​ว่า​มัน​ย้าย​ได้ · แต่​ตอน vector ขยาย มัน​เลือก คัด​ลอก

โปรแกรม​เดียวกัน push element 10,000 ตัว สอง​รอบ ต่าง​กัน​ที่​คีย์เวิร์ด​เดียว

คัด​ลอกย้ายขยาย​กี่​ครั้งความ​จุ​ปลาย​ทาง
move ไม่​ได้​ประกาศ noexcept16,38310,0001516,384
ประกาศ noexcept026,3831516,384

สอง column ขวา​เท่า​กัน​เป๊ะ และ​นั่น​คือ column ที่​สำคัญ​ที่สุด​ของ​ตาราง

มัน​ปิด​คำ​อธิบาย​ที่​ง่าย​ที่สุด​และ​ผิด ว่า​รอบ​หนึ่ง​ขยาย​บ่อย​กว่า​อีก​รอบ · ทั้ง​สอง​รอบ​ขยาย 15 ครั้ง​เท่า​กัน และ​จบ​ที่​ความ​จุ​เดียวกัน · สิ่ง​ที่​ต่าง​คือ​ตอน​ขยาย มัน​เลือก​ทำ​อะไร

ตอน push_back แล้ว​ที่​เต็ม vector ต้อง​จอง​บล็อก​ใหม่​แล้ว​ย้าย​ของ​เดิม​ข้าม​ไป

ระหว่าง​นั้น​มัน​สัญญา​กับ​คุณ​ว่า ถ้า​มี​อะไร​โยน exception ออก​มา vector จะ​ยัง​อยู่​ใน​สภาพ​เดิม เหมือน​ไม่​เคย​เรียก push_back เลย

ลอง​คิด​ว่า​ถ้า​มัน​ย้าย​ไป​ได้​ครึ่ง​ทาง​แล้ว​ตัว​ที่ 5,000 โยน · ของ 4,999 ตัว​แรก​ถูก​ย้าย​ออก​จาก​บล็อก​เดิม ไป​แล้ว บล็อก​เดิม​จึง​พัง และ​บล็อก​ใหม่​ก็​ยัง​ไม่​ครบ · ไม่มี​ทาง​ถอย​กลับ

การ​คัด​ลอก​ไม่มี​ปัญหา​นั้น เพราะ​ของ​เดิม​ยัง​อยู่​ครบ ถ้า​โยนก​ลาง​ทาง​ก็​แค่​ทิ้ง​บล็อก​ใหม่

vector ไม่​ได้​ถาม​ว่า​ย้าย​ได้​ไหม มัน​ถาม​ว่า​ย้าย​แล้ว​โยน​ได้​ไหม

คำถาม​ที่ vector ถาม​คือ “move constructor ตัว​นี้​รับ​ประกัน​ว่า​ไม่​โยน​หรือ​เปล่า” · คำ​ตอบ​มา​จาก​คีย์เวิร์ด noexcept ที่​ประกาศ​ไว้ ไม่​ได้​มา​จาก​การ​ที่ code ข้าง​ใน​ไม่​โยน​จริง

move constructor ที่ ไม่​โยน​แน่ ๆ แต่​ไม่​ได้​ประกาศ ให้​คำ​ตอบ​เดียว​กับ​ตัว​ที่​โยน​ได้​จริง · คอม​ไพ​เลอ​ร์​ไม่​ได้​เดา​ให้ และ vector ก็​ทำ​สิ่ง​ที่​ปลอดภัย​กว่า​เสมอ

นี่​คือ​ส่วน​ที่​หน้า​นี้​ไป​วัด​ต่อ เพราะ​ข้อ​เสีย​ที่​เครื่อง​มือ​เตือน​ได้ ไม่ใช่​ข้อ​เสีย​ที่​น่า​กลัว

เปิด​สวิตช์​เป็น​ชั้น ๆ บน Apple clang version 21.0.0 (clang-2100.1.1.101) แล้ว​นับ​สิ่ง​ที่​มัน​พูด

ชุด​สวิตช์diagnostic ที่​พ่น​ใส่ file ที่​วัด
-O20
-O2 -Wall -Wextra0
ชุด​แรง​สุด​ที่​คอม​ไพ​เลอ​ร์​ตัว​นี้​มี12

ชุด​แรง​สุด​พ่น​ออก​มา 12 ตัว · 11 ตัว​ใน​นั้น​บ่น​ว่า code ของ​คุณ​ใหม่​เกิน​ไป สำหรับ C++98 ซึ่ง​เป็น​มาตรฐาน​ที่​ออก​ก่อน move semantics จะ​มี​อยู่

diagnostic ที่​ชุด​แรง​สุด​พ่น​ออก​มากี่​ตัว
defaulted function definitions are incompatible with C++983
constructor call from initializer list is incompatible with C++982
rvalue references are incompatible with C++982
consecutive right angle brackets are incompatible with C++98 (use '> >')1
default member initializer for non-static data members is incompatible with C++981
generalized initializer lists are incompatible with C++981
include location '/usr/local/include' is unsafe for cross-compilation1
noexcept specifications are incompatible with C++981

ไล่​ดู​ที​ละ​บรรทัด​แล้ว ไม่มี​ตัว​ไหน​พูด​ถึง​การ​คัด​ลอก 16,383 ครั้ง​เลย · ตัว​ที่​เข้า​ใกล้​ที่สุด​คือ noexcept specifications are incompatible with C++98 ซึ่ง​บ่น​ว่า​คุณ ใช้ คีย์เวิร์ด​นั้น ไม่​ได้​บ่น​ว่า​คุณ ไม่​ได้​ใช้ มัน

อีก​คอม​ไพ​เลอ​ร์​หนึ่ง​ให้​ผล​แข็ง​กว่า​นี้

ตัว​วัด​ตัว​เดียวกัน​รัน​บน CI ด้วย GCC 13.3 บน Ubuntu (run 32968018416) ได้​ผล​สอง​อย่าง

  • จำนวน copy เท่า​กัน​เป๊ะ: 16,383 เพราะ libstdc++ ก็​โต​ที​ละ​สอง​เท่า​เหมือน libc++ · เลข​นี้​จึง​ข้าม implementation ที่​โต​สอง​เท่า​ได้ แต่​ยัง​ข้าม​ไป​หา​ตัว​ที่​โต 1.5 เท่า​ไม่​ได้
  • ชุด​สวิตช์​แรง​สุด​ของ GCC พ่น​ออก​มา 0 ตัว เพราะ -Wc++98-compat เป็น​ของ clang เท่านั้น

แปล​ว่า​บน GCC เครื่อง​มือ ไม่​พูด​อะไร​เลย​สัก​คำ ไม่ใช่​พูด​เยอะ​แล้ว​พูด​ผิด​เรื่อง · ข้อ​อ้าง​ของ​หน้า​นี้​จึง​แข็ง​กว่า​บน​คอม​ไพ​เลอ​ร์​ตัว​นั้น ไม่ใช่​อ่อน​กว่า

ศูนย์​ที่​แปล​ว่า​เงียบ กับ​ศูนย์​ที่​แปล​ว่า​สวิตช์​ไม่​ถึง​คอม​ไพ​เลอ​ร์

ตอน​วัด​ครั้ง​แรก ผู้​เขียน​หน้า​นี้​ได้ ศูนย์​ทุก​ชุด​สวิตช์ และ​เกือบ​เขียน​มัน​เป็น​พาด​หัว

ของ​จริง​คือ shell ส่ง -O2 -Weverything ไป​เป็น argument ตัว​เดียว คอม​ไพ​เลอ​ร์​อ่าน​ไม่​ออก แล้ว​ไม่​พ่น​อะไร​เลย · ศูนย์​นั้น​หน้าตา​เหมือน​คำ​ตอบ​ทุก​ประการ

ตัว​วัด​ของ​หน้า​นี้​จึง​มี file ควบคุม​ที่​มี​ตัวแปร​ซึ่ง​ไม่​ถูก​อ่าน และต้อง​ดังทุก​ครั้ง​ที่​สวิตช์​ทำงาน · วัด​ได้ 1 ตัว​ที่ -Wall -Wextra และ 3 ตัว​ที่​ชุด​แรง​สุด · ถ้า​มัน​เงียบ​เมื่อไร ด่าน​แดง​ทันที​และ​บอก​ว่า การ​วัด​พัง ไม่ใช่​ว่า​คอม​ไพ​เลอ​ร์​เงียบ

ประกาศ noexcept บน move constructor และ move assignment ของ​ทุก class ที่​ย้าย​ได้​จริง โดย​ไม่​โยน

class ที่​ถือ pointer, handle, index หรือ std::unique_ptr เข้า​เกณฑ์​นี้​เกือบ​ทั้งหมด เพราะ​การ​ย้าย​ของ​พวก​นี้​คือ​กา​รก๊อป field แล้ว​ล้าง​ของ​เดิม ซึ่ง​ไม่มี​อะไร​ให้​โยน

= default ช่วย​ได้​ใน​หลาย​กรณี เพราะ​คอม​ไพ​เลอ​ร์​อนุมาน noexcept ให้​เอง​เมื่อ​สมาชิก​ทุก​ตัว ย้าย​แบบ​ไม่​โยน · ปัญหา​เกิด​ตอน​คุณ​เขียน move constructor เอง ซึ่ง​เป็น​ตอน​ที่​คุณ​ต้อง​พิมพ์ คีย์เวิร์ด​นั้น​ด้วย​มือ

  • ตัวเลข​ผูก​กับ standard library ที่​รัน​อยู่ Apple clang version 21.0.0 (clang-2100.1.1.101) โต​ที​ละ​สอง​เท่า จึง​ได้ 16,383 · implementation ที่​โต 1.5 เท่า​ให้​ตัวเลข​คนละ​ตัว​โดย​สิ้น​เชิง · อย่า​ยก​ตัวเลข​นี้​ไป​ใช้​โดย​ไม่​ยก​ชื่อ toolchain ไป​ด้วย
  • ไม่​ได้​วัด​เวลา หน่วย​ของ​หน้า​นี้​คือ​จำนวน​ครั้ง​ที่ constructor ถูก​เรียก ซึ่ง​ข้าม​เครื่อง​ได้ ส่วน​วินาที​ข้าม​ไม่​ได้
  • ไม่​ได้​วัด container ตัว​อื่น std::deque กับ container ของ​เอน​จิ​น​เกม​มี​กฎ​การ​ขยาย ของ​ตัว​เอง หน้า​นี้​พูด​ถึง std::vector เท่านั้น
  • ไม่​ได้​วัด​คอม​ไพ​เลอ​ร์​ตัว​อื่น ชุด​สวิตช์​แรง​สุด​ที่​ใช้​คือ -O2 -Weverything ซึ่ง​มี​เฉพาะ​บน​คอม​ไพ​เลอ​ร์​ตระกูล​นี้

ตัว​วัด​อยู่​ที่ scripts/noexcept-realloc/ รัน​ด้วย npm run verify:noexcept · มัน​อยู่​ใน​เกต​ของ CI จึง​มี​คน​รัน​ทุก​ครั้ง​ที่​มี​คน push