ฉันสงสัยว่าการทำสำเนาโค้ดเป็นสิ่งที่จำเป็นหรือไม่เมื่อพูดถึงการเขียนโครงสร้างข้อมูลทั่วไปและ C โดยทั่วไป?
ใน C แน่นอนสำหรับฉันในฐานะคนที่กระเด้งระหว่าง C และ C ++ ฉันซ้ำสิ่งที่น่ารำคาญมากกว่านี้ในชีวิตประจำวันใน C มากกว่าใน C ++ แต่จงใจและฉันไม่จำเป็นต้องมองว่ามันเป็น "ความชั่วร้าย" เพราะอย่างน้อยก็มีประโยชน์ในทางปฏิบัติ - ฉันคิดว่ามันผิดพลาดที่จะพิจารณาทุกสิ่ง อย่างเคร่งครัดว่า "ดี" หรือ "ชั่วร้าย" - เพียงเกี่ยวกับทุกอย่างเป็นเรื่องของการแลกเปลี่ยน การทำความเข้าใจการแลกเปลี่ยนเหล่านั้นอย่างชัดเจนเป็นกุญแจสำคัญที่จะไม่หลีกเลี่ยงการตัดสินใจที่น่าเสียใจในการเข้าใจถึงปัญหาหลังเหตุการณ์และเพียงแค่ติดฉลากสิ่งที่ "ดี" หรือ "ความชั่วร้าย" โดยทั่วไปจะไม่สนใจรายละเอียดย่อยทั้งหมด
ในขณะที่ปัญหาไม่ได้เป็นเอกลักษณ์ของ C ตามที่คนอื่น ๆ ชี้ให้เห็นมันอาจจะรุนแรงมากขึ้นใน C เนื่องจากการขาดสิ่งที่สวยงามกว่าแมโครหรือโมฆะพอยน์เตอร์สำหรับข้อมูลทั่วไปความอึดอัดใจของ OOP ที่ไม่สำคัญและความจริงที่ว่า C ไลบรารีมาตรฐานไม่มีคอนเทนเนอร์ใด ๆ ใน C ++ บุคคลที่ใช้รายการที่เชื่อมโยงของตนเองอาจได้รับกลุ่มคนที่โกรธแค้นที่เรียกร้องว่าเหตุใดพวกเขาจึงไม่ใช้ห้องสมุดมาตรฐานยกเว้นว่าเป็นนักเรียน ใน C คุณจะต้องเชิญม็อบที่โกรธเคืองหากคุณไม่สามารถมั่นใจได้ว่าการนำลิสต์ที่เชื่อมโยงอย่างหรูหราไปใช้ในโหมดสลีปของคุณเนื่องจากโปรแกรมเมอร์ C มักจะคาดหวังให้อย่างน้อยสามารถทำสิ่งเหล่านั้นทุกวัน มัน' ไม่ใช่เพราะความหลงใหลแปลก ๆ ในรายการเชื่อมโยงที่ Linus Torvalds ใช้การดำเนินการค้นหาและลบ SLL โดยใช้การเปลี่ยนทิศทางคู่เป็นเกณฑ์ในการประเมินโปรแกรมเมอร์ที่เข้าใจภาษาและมี "รสนิยมดี" เป็นเพราะโปรแกรมเมอร์ C อาจต้องใช้ตรรกะเช่นนี้นับพันครั้งในอาชีพของพวกเขา ในกรณีนี้สำหรับ C มันเหมือนกับพ่อครัวประเมินทักษะการปรุงอาหารใหม่ด้วยการทำให้พวกเขาเพียงแค่เตรียมไข่เพื่อดูว่าอย่างน้อยพวกเขาก็มีความเชี่ยวชาญในสิ่งพื้นฐานที่พวกเขาจะต้องทำตลอดเวลา
ตัวอย่างเช่นฉันอาจใช้โครงสร้างข้อมูล "รายการฟรีที่จัดทำดัชนี" นี้พื้นฐานโหลใน C ในท้องถิ่นสำหรับแต่ละเว็บไซต์ที่ใช้กลยุทธ์การจัดสรรนี้ (เกือบทุกโครงสร้างที่เชื่อมโยงของฉันเพื่อหลีกเลี่ยงการจัดสรรหนึ่งโหนดในเวลาเดียวและแบ่งครึ่งหน่วยความจำ ค่าใช้จ่ายของการเชื่อมโยงแบบ 64 บิต):
แต่ใน C มันใช้โค้ดจำนวนน้อยมากในrealloc
อาเรย์ที่สามารถเติบโตได้และเก็บหน่วยความจำบางส่วนจากมันโดยใช้วิธีการทำดัชนีไปยังรายการฟรีเมื่อใช้โครงสร้างข้อมูลใหม่ซึ่งใช้อันนี้
ตอนนี้ฉันมีสิ่งเดียวกับที่นำไปใช้ใน C ++ และที่นั่นฉันนำมันไปใช้เพียงครั้งเดียวในฐานะแม่แบบคลาส แต่มันมีความซับซ้อนมากขึ้นในการนำ C ++ ไปใช้กับโค้ดหลายร้อยบรรทัดและการพึ่งพาภายนอกบางอย่างซึ่งก็ครอบคลุมโค้ดหลายร้อยบรรทัด และเหตุผลหลักที่มันมีความซับซ้อนมากขึ้นก็คือฉันต้องเขียนโค้ดกับความคิดที่T
อาจเป็นประเภทข้อมูลใด ๆ ที่เป็นไปได้ มันสามารถโยนในเวลาใดก็ได้ (ยกเว้นเมื่อทำลายมันซึ่งฉันต้องทำอย่างชัดเจนเช่นเดียวกับภาชนะห้องสมุดมาตรฐาน) ฉันต้องคิดเกี่ยวกับการจัดตำแหน่งที่เหมาะสมในการจัดสรรหน่วยความจำสำหรับT
(แม้ว่าจะโชคดีที่มันทำให้ง่ายขึ้นใน C ++ 11 เป็นต้นไป) มันอาจจะไม่สามารถสร้าง / ทำลายได้ง่าย (ต้องมีการวางตำแหน่งใหม่และการเรียกใช้ dtor ด้วยตนเอง) ฉันต้องเพิ่มวิธีการที่ทุกอย่างไม่จำเป็นต้องใช้ และฉันต้องเพิ่มตัววนซ้ำทั้งตัววนซ้ำไม่แน่นอนและอ่านได้อย่างเดียว (const) และอื่น ๆ เป็นต้น
อาร์เรย์ที่เติบโตได้ไม่ใช่วิทยาศาสตร์จรวด
ในคน C ++ ทำให้ดูเหมือนว่าstd::vector
เป็นงานของนักวิทยาศาสตร์จรวดที่ปรับให้เหมาะกับความตาย แต่มันไม่ได้ทำงานได้ดีไปกว่าอาร์เรย์ C แบบไดนามิกที่เข้ารหัสกับประเภทข้อมูลเฉพาะซึ่งเพิ่งใช้realloc
เพื่อเพิ่มความสามารถของอาเรย์เมื่อกดแบ็คด้วย บรรทัดของรหัสโหล ความแตกต่างคือมันต้องใช้การดำเนินการที่ซับซ้อนมากเพื่อให้ลำดับการเข้าถึงแบบสุ่มที่สามารถเติบโตได้เป็นไปตามมาตรฐานอย่างเต็มที่หลีกเลี่ยงการเรียกใช้ ctors ในองค์ประกอบที่ไม่ได้รับการรับรองข้อยกเว้นที่ปลอดภัยให้ทั้ง iter ลักษณะที่จะทำให้กระจ่างเติม ctors จากช่วง ctors สำหรับบางประเภทของT
อาจปฏิบัติกับ POD ที่แตกต่างกันโดยใช้ลักษณะประเภท ฯลฯ ฯลฯ ณ จุดนั้นคุณต้องใช้งานที่ซับซ้อนมากเพียงเพื่อสร้างอาเรย์แบบไดนามิกที่เติบโตได้ แต่เพียงเพราะพยายามจัดการกับทุกกรณีที่เป็นไปได้ ในด้านบวกคุณจะได้รับไมล์สะสมจำนวนมากจากความพยายามพิเศษทั้งหมดหากคุณต้องการเก็บทั้ง PODs และ UDT ที่ไม่น่ารำคาญเอาไว้ใช้สำหรับอัลกอริธึมพื้นฐานตัวทำซ้ำทั่วไปที่ทำงานกับโครงสร้างข้อมูลที่สอดคล้อง ได้รับประโยชน์จากการจัดการข้อยกเว้นและ RAII อย่างน้อยบางครั้งก็แทนที่std::allocator
ด้วยตัวจัดสรรแบบกำหนดเองของคุณเป็นต้น ฯลฯ มันจะได้ผลตอบแทนแน่นอนในไลบรารีมาตรฐานเมื่อคุณพิจารณาว่าคุณได้รับประโยชน์มากเพียงใดstd::vector
มีอยู่ในโลกทั้งโลกของผู้คนที่ใช้มัน แต่สำหรับบางสิ่งที่นำมาใช้ในห้องสมุดมาตรฐานที่ออกแบบมาเพื่อกำหนดเป้าหมายความต้องการทั่วโลก
การใช้งานที่ง่ายขึ้นจัดการกรณีการใช้งานที่เฉพาะเจาะจง
เป็นผลจากการจัดการกรณีการใช้งานที่เฉพาะเจาะจงกับ "รายการฟรีที่จัดทำดัชนี" ของฉันแม้ว่าจะมีการใช้รายการฟรีนี้หลายสิบครั้งบนฝั่ง C และมีรหัสที่ซ้ำกันบางอย่างฉันอาจเขียนรหัสน้อยลง ยอดรวมใน C เพื่อนำไปปฏิบัติที่มากกว่าหนึ่งครั้งกว่าที่ฉันต้องนำไปใช้เพียงครั้งเดียวใน C ++ และฉันต้องใช้เวลาน้อยลงในการบำรุงรักษาการใช้งาน C เหล่านั้นมากกว่าที่ฉันต้องรักษาไว้ใน C ++ หนึ่งในเหตุผลหลักที่ด้าน C นั้นเรียบง่ายมากคือฉันมักจะทำงานกับ PODs ใน C ทุกครั้งที่ฉันใช้เทคนิคนี้และโดยทั่วไปฉันไม่ต้องการฟังก์ชั่นมากกว่าinsert
และerase
ที่ไซต์เฉพาะที่ฉันใช้งานในพื้นที่นี้ โดยทั่วไปฉันสามารถใช้ชุดย่อยที่เล็กที่สุดของการทำงานของรุ่น C ++ ได้เนื่องจากฉันมีอิสระที่จะตั้งสมมติฐานเพิ่มเติมอีกมากมายเกี่ยวกับสิ่งที่ฉันทำและไม่ต้องการการออกแบบเมื่อฉันนำไปใช้เพื่อการใช้งานที่เฉพาะเจาะจงมาก กรณี.
ตอนนี้รุ่น C ++ นั้นดีกว่าและปลอดภัยต่อการใช้งานมาก แต่ก็ยังคงเป็น PITA ที่สำคัญในการติดตั้งและสร้างข้อยกเว้นที่ปลอดภัยและเป็นแบบสองทิศทางเช่นในวิธีที่การใช้งานทั่วไป เวลามากกว่าที่บันทึกไว้ในกรณีนี้จริง ๆ และค่าใช้จ่ายจำนวนมากในการติดตั้งใช้งานในลักษณะทั่วไปไม่เพียง แต่ทำให้เสียเวลา แต่เป็นการทำซ้ำ ๆ ในรูปแบบของสิ่งต่าง ๆ เช่นเวลาสร้างที่เพิ่มขึ้นซึ่งจ่ายเป็นจำนวนมากในแต่ละวัน
ไม่ใช่การโจมตีบน C ++!
แต่นี่ไม่ใช่การโจมตีใน C ++ เพราะฉันรัก C ++ แต่เมื่อพูดถึงโครงสร้างข้อมูลฉันชอบ C ++ เป็นหลักสำหรับโครงสร้างข้อมูลที่ไม่น่าสนใจจริงๆที่ฉันต้องการใช้เวลาพิเศษมากในการติดตั้ง วิธีที่เป็นแบบทั่วไปมากทำให้มีข้อยกเว้นที่ปลอดภัยต่อทุกประเภทที่เป็นไปได้T
ทำตามมาตรฐานและสามารถทำซ้ำได้ ฯลฯ ซึ่งค่าใช้จ่ายล่วงหน้าประเภทนั้นจ่ายจริงในรูปของไมล์สะสม
แต่นั่นก็ส่งเสริมแนวคิดการออกแบบที่แตกต่างกันมาก ใน C ++ ถ้าฉันต้องการ Octree สำหรับการตรวจจับการชนฉันมีแนวโน้มที่จะต้องการทำให้เป็นแบบทั่วไปในระดับที่ n ฉันไม่ต้องการทำให้มันเก็บสามเหลี่ยมตาข่ายที่จัดทำดัชนีไว้ เหตุใดฉันจึงควร จำกัด ให้มีเพียงประเภทข้อมูลเดียวที่สามารถใช้งานได้เมื่อฉันมีกลไกการสร้างรหัสที่มีประสิทธิภาพสูงเพียงแค่ปลายนิ้วซึ่งกำจัดบทลงโทษที่เป็นนามธรรมทั้งหมดที่รันไทม์ ฉันต้องการให้เก็บทรงกลมโพรเซส, ลูกบาศก์, ว็อกเซล, พื้นผิว NURBs, พอยต์คลาวด์, ฯลฯ ฯลฯ และพยายามทำให้มันดีสำหรับทุกอย่างเพราะมันอยากจะออกแบบมันแบบนั้น ฉันอาจไม่ต้องการ จำกัด มันไว้ที่การตรวจจับการชนกัน - วิธีการเกี่ยวกับ raytracing, การเลือกและอื่น ๆ ? C ++ ทำให้ตอนแรกดู "sorta easy" เพื่อสรุปโครงสร้างข้อมูลให้อยู่ในระดับที่ n และนั่นคือวิธีที่ฉันใช้ในการออกแบบดัชนีเชิงพื้นที่ใน C ++ ฉันพยายามออกแบบพวกเขาให้รับมือกับความต้องการความหิวโหยทั่วโลกและสิ่งที่ฉันได้รับจากการแลกเปลี่ยนมักจะเป็น "แจ็คของการซื้อขายทั้งหมด" ด้วยรหัสที่ซับซ้อนมากเพื่อให้สมดุลกับกรณีการใช้งานที่เป็นไปได้ทั้งหมด
แม้ว่าจะสนุกดี แต่ฉันก็สามารถนำดัชนีอวกาศมาใช้ใหม่ได้มากขึ้นในช่วงหลายปีที่ผ่านมาและไม่มีข้อผิดพลาดของ C ++ แต่มีเพียงสิ่งที่ภาษาล่อใจให้ฉันทำเท่านั้น เมื่อฉันเขียนโค้ดบางอย่างเช่น octree ใน C ฉันมีแนวโน้มที่จะทำให้มันทำงานร่วมกับคะแนนและมีความสุขกับมันเพราะภาษาทำให้ยากเกินกว่าที่จะพยายามทำให้เป็นเรื่องทั่วไปในระดับที่ n แต่เนื่องจากแนวโน้มเหล่านั้นฉันจึงมีแนวโน้มที่จะออกแบบสิ่งต่าง ๆ ในช่วงหลายปีที่ผ่านมาซึ่งมีประสิทธิภาพและเชื่อถือได้มากขึ้นและเหมาะสำหรับงานบางอย่างในมือเนื่องจากพวกเขาไม่สนใจที่จะเป็นคนทั่วไปในระดับที่ n พวกเขากลายเป็นเอซในหนึ่งหมวดหมู่พิเศษแทนที่จะเป็นแจ็คของการซื้อขายทั้งหมด อีกครั้งที่มาไม่มีข้อผิดพลาดของ C ++ แต่เพียงแนวโน้มของมนุษย์ที่ฉันมีเมื่อฉันใช้มันเมื่อเทียบกับซี
แต่อย่างไรก็ตามฉันรักทั้งสองภาษา แต่มีแนวโน้มที่แตกต่างกัน ใน CI มีแนวโน้มที่จะไม่พูดคุยพอ ใน C ++ ฉันมีแนวโน้มที่จะพูดคุยมากเกินไป การใช้ทั้งสองอย่างช่วยให้ฉันสร้างความสมดุลให้กับตัวเอง
การใช้งานทั่วไปเป็นบรรทัดฐานหรือคุณเขียนการใช้งานที่แตกต่างกันสำหรับแต่ละกรณีการใช้งาน?
สำหรับสิ่งเล็ก ๆ น้อย ๆ เช่นรายการที่มีการเชื่อมโยงแบบ 32 บิตโดยใช้โหนดจากอาเรย์หรืออาเรย์ที่จัดสรรใหม่เอง (เทียบเท่าแบบอะนาล็อกเท่ากับstd::vector
ใน C ++) หรือพูดว่า octree ที่เก็บคะแนนไว้และตั้งเป้าที่จะไม่ทำอะไรมากกว่านี้ ไม่ต้องพูดถึงเรื่องการจัดเก็บข้อมูลชนิดใด ๆ ฉันใช้สิ่งเหล่านี้เพื่อจัดเก็บประเภทข้อมูลเฉพาะ (แม้ว่ามันอาจจะเป็นนามธรรมและใช้ตัวชี้ฟังก์ชันในบางกรณี แต่อย่างน้อยก็เฉพาะเจาะจงกว่าการพิมพ์เป็ดด้วยความหลากหลายแบบคงที่)
และฉันมีความสุขอย่างสมบูรณ์แบบด้วยความซ้ำซ้อนเล็กน้อยในกรณีเหล่านี้หากว่าฉันทดสอบหน่วยอย่างละเอียด หากฉันไม่ทดสอบหน่วยความซ้ำซ้อนก็เริ่มรู้สึกไม่สบายใจมากขึ้นเพราะคุณอาจมีรหัสซ้ำซ้อนที่อาจทำซ้ำข้อผิดพลาดได้เช่นจากนั้นแม้ว่าประเภทของรหัสที่คุณเขียนนั้นไม่น่าจะต้องเปลี่ยนแปลงการออกแบบใด ๆ มันอาจยังต้องการการเปลี่ยนแปลงเพราะมันเสีย ฉันมักจะเขียนการทดสอบหน่วยอย่างละเอียดยิ่งขึ้นสำหรับรหัส C ที่ฉันเขียนด้วยเหตุผล
สำหรับสิ่งที่ไม่สำคัญฉันมักจะใช้ C ++ แต่ถ้าฉันจะนำไปใช้ใน C ฉันจะลองใช้พvoid*
อยน์เตอร์อาจยอมรับขนาดของประเภทเพื่อทราบว่าหน่วยความจำจัดสรรจำนวนเท่าใดสำหรับแต่ละองค์ประกอบและอาจเป็นcopy/destroy
ฟังก์ชันพอยน์เตอร์ เพื่อทำสำเนาลึกและทำลายข้อมูลหากไม่สามารถสร้าง / ทำลายได้ง่าย เวลาส่วนใหญ่ที่ฉันไม่ได้รำคาญและไม่ได้ใช้ C มากนักในการสร้างโครงสร้างข้อมูลและอัลกอริทึมที่ซับซ้อนที่สุด
หากคุณใช้โครงสร้างข้อมูลหนึ่งบ่อยครั้งเพียงพอกับชนิดข้อมูลเฉพาะคุณยังสามารถห่อรุ่นที่มีความปลอดภัยมากกว่าหนึ่งที่ทำงานกับบิตและไบต์และตัวชี้ฟังก์ชั่นและvoid*
เช่นเพื่อกำหนดความปลอดภัยของประเภทผ่าน C wrapper
ฉันสามารถลองเขียนการใช้งานทั่วไปสำหรับแผนที่แฮช แต่ฉันมักจะพบว่าผลลัพธ์สุดท้ายจะยุ่งเหยิง ฉันยังสามารถเขียนการใช้งานเฉพาะสำหรับกรณีการใช้งานเฉพาะนี้เก็บรหัสที่ชัดเจนและง่ายต่อการอ่านและตรวจแก้จุดบกพร่อง แน่นอนหลังจะนำไปสู่การทำสำเนารหัสบางอย่าง
ตารางแฮชเป็นเรื่องที่ไม่แน่นอนเพราะมันอาจนำไปใช้งานหรือซับซ้อนมากขึ้นอยู่กับความซับซ้อนของความต้องการของคุณที่เกี่ยวกับแฮช, การทำซ้ำหากคุณต้องการให้ตารางโตขึ้นเองโดยอัตโนมัติหรือคาดการณ์ขนาดของตาราง ล่วงหน้าไม่ว่าคุณจะใช้ที่อยู่เปิดหรือแยกการเชื่อมโยงอื่น ๆ แต่สิ่งหนึ่งที่ต้องจำไว้คือถ้าคุณปรับแต่งตารางแฮชให้เหมาะกับความต้องการของไซต์เฉพาะมักจะไม่ซับซ้อนในการนำไปใช้และมักจะชนะ ไม่ซ้ำซ้อนเมื่อปรับแต่งให้ตรงกับความต้องการเหล่านั้น อย่างน้อยนั่นเป็นข้อแก้ตัวที่ฉันให้กับตัวเองถ้าฉันใช้บางอย่างในพื้นที่ หากไม่ใช่คุณอาจใช้วิธีที่อธิบายข้างต้นด้วยvoid*
และพอยน์เตอร์ฟังก์ชั่นเพื่อคัดลอก / ทำลายสิ่งต่าง ๆ และทำให้เป็นแนวทั่วไป
บ่อยครั้งที่ไม่ต้องใช้ความพยายามมากหรือใช้รหัสมากในการเอาชนะโครงสร้างข้อมูลที่ได้รับการวางนัยทั่วไปหากทางเลือกของคุณนั้นมีความเหมาะสมอย่างยิ่งกับกรณีการใช้งานที่แน่นอนของคุณ ตัวอย่างเช่นมันเป็นเรื่องเล็กน้อยที่จะเอาชนะประสิทธิภาพของการใช้งานmalloc
สำหรับแต่ละโหนด (เมื่อเทียบกับการรวมหน่วยความจำจำนวนมากสำหรับโหนดจำนวนมาก) เพียงครั้งเดียวและสำหรับทุกคนด้วยรหัสที่คุณไม่ต้องกลับไปใช้งาน แม้ในขณะที่การใช้งานใหม่ของการmalloc
ออกมา มันอาจใช้เวลาชั่วชีวิตในการเอาชนะและรหัสไม่ซับซ้อนน้อยกว่าที่คุณต้องอุทิศส่วนใหญ่ของชีวิตของคุณเพื่อรักษาและทำให้มันทันสมัยถ้าคุณต้องการให้ตรงกับความทั่วไป
เป็นอีกตัวอย่างหนึ่งที่ฉันมักจะพบว่ามันง่ายมากที่จะใช้งานโซลูชันที่เร็วกว่า 10 เท่าหรือมากกว่า VFX solution ที่ Pixar หรือ Dreamworks มอบให้ ฉันสามารถทำได้ในการนอนหลับของฉัน แต่นั่นไม่ใช่เพราะการใช้งานของฉันดีกว่า - ห่างไกลจากมัน พวกเขาด้อยกว่าคนส่วนใหญ่อย่างสิ้นเชิง มันยอดเยี่ยมมากสำหรับเคสการใช้งานที่เฉพาะเจาะจงของฉัน เวอร์ชันของฉันอยู่ไกลโดยทั่วไปใช้น้อยกว่า Pixar หรือ Dreamwork เป็นการเปรียบเทียบที่ไม่ยุติธรรมอย่างน่าขันเนื่องจากโซลูชันของพวกเขายอดเยี่ยมมากเมื่อเทียบกับโซลูชันที่เรียบง่ายของฉัน แต่นั่นเป็นประเด็น การเปรียบเทียบไม่จำเป็นต้องยุติธรรม หากสิ่งที่คุณต้องการคือสิ่งที่เฉพาะเจาะจงบางอย่างคุณไม่จำเป็นต้องให้โครงสร้างข้อมูลจัดการกับรายการสิ่งที่คุณไม่ต้องการ
บิตและไบต์ที่เป็นเนื้อเดียวกัน
สิ่งหนึ่งที่จะใช้ประโยชน์จาก C เนื่องจากมีการขาดความปลอดภัยประเภทโดยธรรมชาติคือความคิดของการจัดเก็บสิ่งที่เป็นเนื้อเดียวกันตามลักษณะของบิตและไบต์ มีความไม่ชัดเจนเกิดขึ้นระหว่างตัวจัดสรรหน่วยความจำและโครงสร้างข้อมูล
แต่การจัดเก็บสิ่งต่าง ๆ ที่มีขนาดต่างกันหรือแม้แต่สิ่งต่าง ๆ ที่อาจมีขนาดต่างกันเช่น polymorphic Dog
และCat
ทำได้ยาก คุณไม่สามารถไปตามสมมติฐานที่ว่าพวกเขาอาจมีขนาดตัวแปรและเก็บไว้อย่างต่อเนื่องในภาชนะเข้าถึงแบบสุ่มง่าย ๆ เพราะความก้าวหน้าที่จะได้รับจากองค์ประกอบหนึ่งไปยังองค์ประกอบต่อไปอาจแตกต่างกัน จากผลของการจัดเก็บรายการที่มีทั้งสุนัขและแมวคุณอาจต้องใช้โครงสร้างข้อมูล / การจัดสรร 3 ครั้งแยกกัน (หนึ่งรายการสำหรับสุนัขหนึ่งรายการสำหรับแมวและอีกรายการหนึ่งสำหรับรายการ polymorphic ของตัวชี้ฐานหรือตัวชี้สมาร์ทหรือแย่กว่านั้น จัดสรรสุนัขแต่ละตัวและแมวกับผู้จัดสรรที่มีวัตถุประสงค์ทั่วไปและกระจายไปทั่วหน่วยความจำ) ซึ่งมีราคาแพงและมีส่วนแบ่งในแคชที่เพิ่มขึ้นหลายเท่า
ดังนั้นกลยุทธ์หนึ่งที่จะใช้ใน C แม้ว่ามันจะลดความร่ำรวยและความปลอดภัยของประเภทที่ลดลงก็คือการสรุปในระดับบิตและไบต์ คุณอาจจะสามารถสันนิษฐานได้ว่าDogs
และCats
ต้องการจำนวนบิตและไบต์เท่ากันมีเขตข้อมูลเดียวกันตัวชี้เดียวกันกับตารางตัวชี้ฟังก์ชัน แต่ในการแลกเปลี่ยนคุณสามารถโค้ดโครงสร้างข้อมูลได้น้อยลง แต่ที่สำคัญคือเก็บสิ่งเหล่านี้ได้อย่างมีประสิทธิภาพและต่อเนื่อง คุณกำลังรักษาสุนัขและแมวอย่างสหภาพแรงงานแบบอะนาล็อกในกรณีนั้น (หรือคุณอาจใช้สหภาพ)
และนั่นทำให้เกิดค่าใช้จ่ายมหาศาลในการพิมพ์ความปลอดภัย หากมีสิ่งหนึ่งที่ฉันคิดถึงมากกว่าสิ่งอื่นใดใน C ก็เป็นความปลอดภัยประเภท มันเข้าใกล้ระดับแอสเซมบลีมากขึ้นซึ่งมีโครงสร้างที่ระบุจำนวนหน่วยความจำที่จัดสรรและวิธีการจัดตำแหน่งข้อมูลแต่ละฟิลด์ แต่นั่นเป็นเหตุผลข้อแรกของฉันที่จะใช้ C ถ้าคุณพยายามควบคุมเลย์เอาต์ของหน่วยความจำและทุกอย่างถูกจัดสรรและที่เก็บทุกอย่างสัมพันธ์กันบ่อยครั้งมันช่วยให้คุณคิดถึงสิ่งต่าง ๆ ในระดับบิตและ ไบต์และจำนวนบิตและไบต์ที่คุณต้องใช้เพื่อแก้ไขปัญหาเฉพาะ ความโง่ของระบบประเภท C นั้นจะมีประโยชน์มากกว่าการเป็นแฮนดิแคป โดยทั่วไปแล้วจะสิ้นสุดทำให้ประเภทข้อมูลน้อยลงที่จะจัดการกับ
การทำสำเนา Illusory / Apparent
ตอนนี้ฉันใช้ "การทำซ้ำ" ในแง่ที่หลวมสำหรับสิ่งต่าง ๆ ที่อาจไม่ซ้ำซ้อน ฉันเคยเห็นผู้คนต่างแยกแยะความแตกต่างเช่นการซ้ำซ้อน "บังเอิญ / ชัดเจน" จาก "การซ้ำซ้อนจริง" วิธีที่ฉันเห็นมันคือไม่มีความแตกต่างที่ชัดเจนในหลาย ๆ กรณี ฉันพบความแตกต่างเช่น "ความเป็นไปได้ที่ไม่เหมือนใคร" และ "ความซ้ำซ้อนที่อาจเกิดขึ้น" และมันสามารถไปได้ทั้งทาง บ่อยครั้งขึ้นอยู่กับว่าคุณต้องการให้การออกแบบและการนำไปใช้งานของคุณมีวิวัฒนาการอย่างไรและปรับแต่งให้เข้ากับกรณีการใช้งานเฉพาะอย่างสมบูรณ์แบบอย่างไร แต่บ่อยครั้งที่ฉันพบว่าสิ่งที่อาจปรากฏเป็นการทำสำเนารหัสในภายหลังกลายเป็นไม่ซ้ำซ้อนหลังจากการปรับปรุงซ้ำหลายครั้ง
ใช้เวลาดำเนินการอาร์เรย์ growable ง่ายโดยใช้เทียบเท่ากระเชอของrealloc
std::vector<int>
เริ่มแรกมันอาจจะซ้ำซ้อนกับพูดใช้std::vector<int>
ใน C ++ แต่คุณอาจพบว่าอาจเป็นประโยชน์ในการจัดสรรล่วงหน้า 64 ไบต์ล่วงหน้าเพื่อให้สามารถแทรกจำนวนเต็ม 32 บิตสิบหก 32 บิตได้โดยไม่ต้องจัดสรรฮีป std::vector<int>
ตอนนี้ก็ยังไม่มีการซ้ำซ้อนอีกต่อไปอย่างน้อยก็ไม่ได้ด้วย จากนั้นคุณอาจพูดว่า "แต่ฉันสามารถสรุปสิ่งนี้กับสิ่งใหม่SmallVector<int, 16>
และคุณทำได้ แต่จากนั้นสมมติว่าคุณพบว่ามันมีประโยชน์เพราะสิ่งเหล่านี้มีขนาดเล็กมากอายุสั้น ๆ เพื่อเพิ่มความจุของอาร์เรย์ให้กับการจัดสรรฮีป เพิ่มขึ้น 1.5 (ประมาณจำนวนที่มากvector
การใช้งานใช้) ในขณะที่ทำงานนอกสมมติฐานที่ว่าความจุของอาเรย์นั้นเป็นพลังของสองเสมอ ตอนนี้คอนเทนเนอร์ของคุณแตกต่างกันมากและอาจไม่มีคอนเทนเนอร์เหมือน และบางทีคุณอาจพยายามทำให้พฤติกรรมทั่วไปโดยเพิ่มพารามิเตอร์เทมเพลตมากขึ้นเพื่อกำหนดค่าผู้ช่วยให้รอดล่วงหน้ากำหนดพฤติกรรมการจัดสรรใหม่เป็นต้น แต่ ณ จุดนี้คุณอาจพบบางสิ่งที่ไม่น่าใช้จริง ๆ เมื่อเทียบกับบรรทัดโหลง่าย ๆ ของ C รหัส.
และคุณอาจถึงจุดที่คุณต้องการโครงสร้างข้อมูลซึ่งจัดสรรหน่วยความจำแบบ 256 บิตและแผ่นรองที่มีการจัดตำแหน่ง POD เฉพาะสำหรับคำสั่ง AVX 256, จัดสรรล่วงหน้า 128 ไบต์เพื่อหลีกเลี่ยงการจัดสรรฮีปสำหรับอินพุตขนาดเล็กในกรณีทั่วไป เต็มและอนุญาตให้เขียนทับองค์ประกอบของส่วนต่อท้ายที่ปลอดภัยเกินขนาดอาร์เรย์ แต่ไม่เกินความจุของอาร์เรย์ ณ จุดนี้ถ้าคุณยังคงพยายามที่จะสรุปวิธีการแก้ปัญหาเพื่อหลีกเลี่ยงการทำซ้ำรหัส C จำนวนเล็กน้อยอาจเทพเทพแห่งการเขียนโปรแกรมมีเมตตาต่อจิตวิญญาณของคุณ
ดังนั้นยังมีเวลาเช่นนี้สิ่งที่เริ่มแรกเริ่มมองหาซ้ำซ้อนเริ่มเติบโตในขณะที่คุณปรับวิธีการแก้ปัญหาให้ดีขึ้นและดีขึ้นและดีขึ้นพอดีกับกรณีการใช้งานบางอย่างเป็นสิ่งที่ไม่ซ้ำกันทั้งหมดและไม่ซ้ำซ้อน แต่นั่นเป็นเพียงสิ่งที่คุณสามารถปรับแต่งให้เหมาะกับกรณีการใช้งานเฉพาะ บางครั้งเราแค่ต้องการสิ่งที่ "ดี" ซึ่งเป็นข้อมูลทั่วไปสำหรับจุดประสงค์ของเราและที่นั่นฉันได้รับประโยชน์มากที่สุดจากโครงสร้างข้อมูลทั่วไป แต่สำหรับสิ่งที่ยอดเยี่ยมที่ทำอย่างสมบูรณ์แบบสำหรับกรณีการใช้งานเฉพาะความคิดของ "วัตถุประสงค์ทั่วไป" และ "ทำอย่างสมบูรณ์แบบสำหรับวัตถุประสงค์ของฉัน" เริ่มที่จะไม่เข้ากันเกินไป
POD และดั้งเดิม
ตอนนี้ใน C ฉันมักพบข้ออ้างในการจัดเก็บ POD และโดยเฉพาะอย่างยิ่งในโครงสร้างข้อมูลเมื่อใดก็ตามที่เป็นไปได้ นั่นอาจดูเหมือนรูปแบบการต่อต้าน แต่จริง ๆ แล้วฉันพบว่ามันมีประโยชน์โดยไม่ตั้งใจในการปรับปรุงความสามารถในการบำรุงรักษาของรหัสกับประเภทของสิ่งที่ฉันเคยทำบ่อยขึ้นใน C ++
ตัวอย่างง่ายๆคือการฝึกงานสตริงสั้น ๆ (ตามปกติจะเป็นกรณีที่มีสตริงที่ใช้สำหรับคีย์การค้นหา - พวกเขามีแนวโน้มที่จะสั้นมาก) ทำไมต้องจัดการกับสตริงที่มีความยาวแปรผันเหล่านี้ซึ่งมีขนาดแตกต่างกันไปในขณะรันไทม์ซึ่งหมายถึงโครงสร้างที่ไม่สำคัญและการทำลายล้าง (เนื่องจากเราอาจต้องจัดสรรฮีปและฟรี) ลองเก็บสิ่งเหล่านี้ไว้ในโครงสร้างข้อมูลส่วนกลางเช่น trie แบบปลอดภัยของเธรดหรือตารางแฮชที่ออกแบบมาเพื่อใช้กับการฝึกสตริงเท่านั้นและจากนั้นอ้างอิงสตริงเหล่านั้นด้วยแบบเก่าint32_t
หรือ
struct IternedString
{
int32_t index;
};
... ในตารางแฮชต้นไม้สีแดงดำข้ามรายการ ฯลฯ หากเราไม่ต้องการเรียงลำดับพจนานุกรม? ตอนนี้ทั้งหมดของโครงสร้างข้อมูลอื่น ๆ ของเราซึ่งเรารหัสเพื่อทำงานร่วมกับจำนวนเต็ม 32 บิตในขณะนี้สามารถเก็บปุ่มสตริงเหล่านี้ interned ที่มีประสิทธิภาพเพียง ints
32 และฉันพบว่าในกรณีการใช้งานของฉันอย่างน้อย (อาจเป็นโดเมนของฉันตั้งแต่ฉันทำงานในพื้นที่เช่น raytracing, การประมวลผลตาข่าย, การประมวลผลภาพ, ระบบอนุภาค, ผูกพันกับภาษาสคริปต์, การใช้งานชุด GUI แบบมัลติเธรดต่ำ ฯลฯ - สิ่งต่าง ๆ ในระดับต่ำ แต่ไม่ใช่ระดับต่ำเป็นระบบปฏิบัติการ) รหัสนั้นเกิดขึ้นโดยบังเอิญเพื่อให้มีประสิทธิภาพมากขึ้นและง่ายขึ้นเพียงแค่จัดเก็บดัชนีไปยังสิ่งต่าง ๆ เช่นนี้ มันทำให้ฉันมักจะทำงานพูด 75% ของเวลาด้วยความยุติธรรมint32_t
และfloat32
ในโครงสร้างข้อมูลที่ไม่สำคัญของฉันหรือเพียงแค่จัดเก็บสิ่งต่าง ๆ ที่มีขนาดเท่ากัน (เกือบตลอดเวลา 32 บิต)
และโดยธรรมชาติหากเหมาะสมกับกรณีของคุณคุณสามารถหลีกเลี่ยงการใช้งานโครงสร้างข้อมูลจำนวนมากสำหรับประเภทข้อมูลที่แตกต่างกันเนื่องจากคุณจะทำงานกับผู้ใช้จำนวนน้อยในตอนแรก
การทดสอบและความน่าเชื่อถือ
สิ่งสุดท้ายที่ฉันเสนอและอาจไม่เหมาะสำหรับทุกคนคือการทดสอบการเขียนสำหรับโครงสร้างข้อมูลเหล่านั้น ทำให้ดีจริงๆในบางสิ่ง ตรวจสอบให้แน่ใจว่าพวกเขาเชื่อถือได้เป็นพิเศษ
การทำสำเนารหัสย่อยบางครั้งจะกลายเป็นสิ่งที่ลืมได้มากขึ้นเนื่องจากการทำสำเนารหัสเป็นภาระการบำรุงรักษาหากคุณต้องทำการเปลี่ยนแปลงแบบเรียงซ้อนกับรหัสที่ทำซ้ำ คุณกำจัดหนึ่งในเหตุผลสำคัญที่ทำให้รหัสซ้ำซ้อนนั้นเปลี่ยนไปโดยทำให้แน่ใจว่ามันน่าเชื่อถือเป็นพิเศษและเหมาะสมกับสิ่งที่มันไม่ได้ทำ
ความรู้สึกของฉันของความสวยงามได้เปลี่ยนไปในช่วงหลายปีที่ผ่านมา ฉันไม่หงุดหงิดอีกต่อไปเพราะฉันเห็นห้องสมุดแห่งหนึ่งใช้ผลิตภัณฑ์ dot หรือตรรกะ SLL เล็ก ๆ น้อย ๆ ที่นำไปใช้ในอีกไลบรารีหนึ่ง ฉันรู้สึกหงุดหงิดเมื่อสิ่งต่าง ๆ ผ่านการทดสอบต่ำและไม่น่าเชื่อถือและฉันก็พบว่าความคิดสร้างสรรค์มากขึ้น ฉันได้รับการจัดการอย่างแท้จริงกับฐานรหัสที่ทำซ้ำข้อบกพร่องผ่านรหัสที่ซ้ำกันและได้เห็นกรณีที่เลวร้ายที่สุดของการเข้ารหัสคัดลอกและวางทำให้สิ่งที่ควรได้รับการเปลี่ยนแปลงเล็กน้อยเพื่อเปลี่ยนสถานที่ส่วนกลางให้กลายเป็นข้อผิดพลาด หลายครั้งที่มันเป็นผลมาจากการทดสอบที่ไม่ดีของรหัสไม่สามารถเชื่อถือได้และดีในสิ่งที่มันทำในตอนแรก ก่อนหน้านี้เมื่อฉันทำงานในฐานรหัสรถบักกี้แบบดั้งเดิม ใจของฉันเชื่อมโยงทุกรูปแบบของการทำสำเนารหัสว่ามีความเป็นไปได้สูงมากในการทำซ้ำข้อบกพร่องและต้องการการเปลี่ยนแปลงแบบเรียงซ้อน แต่ห้องสมุดขนาดเล็กที่ทำสิ่งหนึ่งได้ดีมากและน่าเชื่อถือจะพบว่ามีเหตุผลน้อยมากที่จะเปลี่ยนแปลงในอนาคตแม้ว่ามันจะมีโค้ดที่ดูซ้ำซ้อนอยู่ที่นี่และที่นั่นก็ตาม ลำดับความสำคัญของฉันถูกปิดไปแล้วเมื่อทำซ้ำฉันหงุดหงิดมากกว่าคุณภาพไม่ดีและขาดการทดสอบ สิ่งหลังนี้ควรเป็นสิ่งที่สำคัญที่สุด
การทำสำเนารหัสสำหรับ Minimalism?
นี่เป็นความคิดที่ตลกที่โผล่ขึ้นมาในหัวของฉัน แต่ลองพิจารณากรณีที่เราอาจพบห้องสมุด C และ C ++ ซึ่งทำสิ่งเดียวกันโดยประมาณ: ทั้งสองมีฟังก์ชั่นเดียวกันโดยประมาณการจัดการข้อผิดพลาดจำนวนเท่ากัน มีประสิทธิภาพมากกว่าที่อื่น ๆ และที่สำคัญที่สุดทั้งสองอย่างนั้นถูกนำไปใช้อย่างมีประสิทธิภาพทดสอบอย่างดีและเชื่อถือได้ น่าเสียดายที่ฉันต้องพูดอย่างสมมุติที่นี่เพราะฉันไม่เคยพบอะไรที่ใกล้เคียงกับการเปรียบเทียบแบบเคียงข้างกัน แต่สิ่งที่ใกล้เคียงที่สุดที่ฉันเคยพบในการเปรียบเทียบแบบเคียงข้างกันนี้มักจะมีไลบรารี C มากน้อยกว่า C ++ เทียบเท่า (บางครั้ง 1 / 10th ของขนาดรหัสของมัน)
และฉันเชื่อว่าเหตุผลนั้นเป็นเพราะอีกครั้งเพื่อแก้ปัญหาด้วยวิธีทั่วไปที่จัดการกรณีการใช้ที่หลากหลายที่สุดแทนที่จะเป็นกรณีการใช้งานที่แน่นอนหนึ่งกรณีอาจต้องใช้โค้ดหลายร้อยถึงพันบรรทัดในขณะที่หลังอาจต้องการเพียง หนึ่งโหล. ทั้งๆที่มีความซ้ำซ้อนและทั้งๆที่ห้องสมุดมาตรฐาน C เป็นสุดยอดเมื่อมันมาถึงการให้โครงสร้างข้อมูลมาตรฐานมันมักจะจบลงด้วยการผลิตรหัสน้อยลงในมือมนุษย์เพื่อแก้ปัญหาเดียวกันและฉันคิดว่ามันเป็นเพราะ เพื่อความแตกต่างในแนวโน้มของมนุษย์ระหว่างสองภาษานี้ หนึ่งส่งเสริมการแก้ปัญหากับกรณีการใช้งานที่เฉพาะเจาะจงอื่น ๆ มีแนวโน้มที่จะส่งเสริมการแก้ปัญหาที่เป็นนามธรรมและทั่วไปมากขึ้นกับกรณีการใช้ที่หลากหลายที่สุด แต่ผลลัพธ์สุดท้ายของสิ่งเหล่านี้ไม่ได้
ฉันกำลังดู raytracer ของใครบางคนใน GitHub วันอื่น ๆ และมันถูกนำมาใช้ใน C ++ และจำเป็นต้องใช้รหัสมากสำหรับของเล่น raytracer และฉันไม่ได้ใช้เวลามากในการดูโค้ด แต่มีโครงสร้างของวัตถุประสงค์ทั่วไปในเรือที่มีวิธีการจัดการมากกว่าวิธีที่ raytracer ต้องการ และฉันรู้ว่ารูปแบบของการเข้ารหัสนั้นเพราะฉันเคยใช้ C ++ แบบเดียวกันในรูปแบบสุดยอดนิยมโดยมุ่งเน้นที่การสร้างไลบรารี่แบบเต็มรูปแบบของโครงสร้างข้อมูลที่มีวัตถุประสงค์ทั่วไปเป็นอย่างแรก ปัญหาที่มือแล้วจัดการปัญหาที่แท้จริงที่สอง แต่ในขณะที่โครงสร้างทั่วไปเหล่านั้นอาจกำจัดความซ้ำซ้อนบางอย่างที่นี่และที่นั่นและเพลิดเพลินกับการใช้ซ้ำในบริบทใหม่ ในการแลกเปลี่ยนพวกเขาทำให้โครงการขยายตัวอย่างมหาศาลโดยการแลกเปลี่ยนความซ้ำซ้อนเล็กน้อยด้วยการโหลดโค้ด / ฟังก์ชั่นที่ไม่จำเป็นและจำนวนมากไม่จำเป็นต้องบำรุงรักษาง่ายกว่าในอดีต ในทางตรงกันข้ามฉันมักจะพบว่ามันยากกว่าที่จะรักษาเพราะมันยากที่จะรักษาการออกแบบของสิ่งทั่วไปที่มีการตัดสินใจออกแบบสมดุลสมดุลกับความต้องการที่กว้างที่สุดเท่าที่จะทำได้