การทำสำเนารหัสเป็นความชั่วร้ายที่จำเป็นใน C หรือไม่


16

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

ฉันสามารถลองเขียนการใช้งานทั่วไปเป็นhash mapตัวอย่างได้ แต่ฉันมักจะพบว่าผลลัพธ์สุดท้ายจะยุ่งเหยิง ฉันยังสามารถเขียนการใช้งานเฉพาะสำหรับกรณีการใช้งานเฉพาะนี้เก็บรหัสที่ชัดเจนและง่ายต่อการอ่านและตรวจแก้จุดบกพร่อง แน่นอนหลังจะนำไปสู่การทำสำเนารหัสบางอย่าง

การใช้งานทั่วไปเป็นบรรทัดฐานหรือคุณเขียนการใช้งานที่แตกต่างกันสำหรับแต่ละกรณีการใช้งาน?


11
ความไม่แน่ใจทางศีลธรรมที่คุณกำลังอธิบายนั้นไม่ได้เป็นเอกลักษณ์ของ C. โดยทั่วไปฉันพบว่าแถบสำหรับการสร้างการใช้งานทั่วไปนั้นค่อนข้างสูงเว้นแต่คุณจะมีผู้ชมอยู่ จำนวนของความพยายามที่จำเป็นในการสร้างการใช้งานทั่วไปที่ดีนั้นไกลเกินกว่าจุดแก้ปัญหาคือ IME
Robert Harvey


1
@kevincline: ดี Java มี generics จริงอยู่มันเป็น "อย่าใส่ใจคนที่อยู่หลังม่าน" ประเภททั่วไป
Robert Harvey

1
และก่อนหน้า generics คุณสามารถผ่านใน Object และส่งผ่านใน Float, Double, Integer และ Long ตามลำดับ
Daniel Kaplan

3
Deja vu เป็นรหัสตรวจสอบกลิ่นและความต้องการ DRY เป็นรากฐานของการปฏิบัติที่ดีที่สุดทั้งหมดที่ควรค่าแก่การใส่ใจกับ IMO แต่ฉันได้ยิงตัวเองด้วยภาษา OOP ที่พยายามอย่างหนักเกินไปที่จะหลีกเลี่ยงการทำซ้ำเพื่อประโยชน์ของตัวเอง หากคุณไม่ผูกพันข้อกังวลที่ไม่เกี่ยวข้องกันโดยไม่จำเป็นหรือทำลายความชัดเจนเพื่อประโยชน์ของฟังก์ชันที่คล้ายกันทั้งสองกลายเป็นหนึ่งฉันจะไปกับสัญชาตญาณในทุก ๆ ภาษา
Erik Reppen

คำตอบ:


27

C ทำให้ยากต่อการเขียนรหัสทั่วไป แตกต่างจาก C ++ ซึ่งให้แม่แบบและฟังก์ชันเสมือน C มีเพียง 3 กลไกสำหรับการเขียนโค้ดทั่วไป:

  1. void* ชี้
  2. มาโครตัวประมวลผลล่วงหน้า
  3. พอยน์เตอร์ฟังก์ชั่น

void* พอยน์เตอร์นั้นห่างไกลจากอุดมคติเนื่องจากคุณสูญเสียความปลอดภัยทุกประเภทที่คอมไพเลอร์ให้มาซึ่งอาจส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดที่ยากต่อการดีบักซึ่งเป็นผลมาจากการปลดเปลื้องประเภทที่ไม่ถูกต้อง

มาโครของตัวประมวลผลล่วงหน้ามีข้อบกพร่องที่น่าสังเกต - การขยายตัวของตัวประมวลผลล่วงหน้านั้นเป็นเพียงกลไกการค้นหา / แทนที่ที่เกิดขึ้นก่อนขั้นตอนการรวบรวมซึ่งอีกครั้งอาจส่งผลให้เกิดข้อผิดพลาดที่ยากต่อการดีบัก เทพบางสิ่งบางอย่างเช่นความเป็นอยู่เช่น#define add(x) (x+x)ที่สามารถเพิ่มขึ้นสองครั้งถ้าคุณเรียกx add(i++)คุณสามารถเขียนแม่แบบสไตล์รหัสทั่วไปอย่างสิ้นเชิงโดยใช้ C-แมโคร แต่ผลที่ได้คือจริงๆน่าเกลียดและยากที่จะรักษา

คำแนะนำการทำงานให้เป็นวิธีที่ดีที่จะเขียนโค้ดทั่วไป แต่โชคร้ายที่พวกเขาไม่ได้ให้คุณกับประเภททั่วไป - พวกเขาเพียงให้เป็นไปได้ของความแตกต่างเวลาทำงาน (ซึ่งเป็นเหตุผลที่ยกตัวอย่างเช่นห้องสมุดมาตรฐานqsortยังคงต้องฟังก์ชั่นที่ใช้void*ชี้.)

คุณยังสามารถนำลำดับชั้นของคลาสไปใช้ใน C โดยใช้ struct เช่นเดียวกับที่ทำในไลบรารี GLib ซึ่งจัดเตรียมGObjectคลาสฐานทั่วไป แต่สิ่งนี้มีปัญหาคล้ายกันกับการใช้พvoid*อยน์เตอร์เนื่องจากคุณยังต้องพึ่งพาการส่งแบบแมนนวลที่ไม่ปลอดภัยเพื่อการโยนขึ้นและลง

ดังนั้นใช่ C ทำให้ยากที่จะเขียนโค้ดที่เป็นทั้งแบบทั่วไปและปลอดภัย / ง่ายต่อการบำรุงรักษาซึ่งน่าเสียดายที่อาจส่งผลให้มีการทำสำเนารหัส โครงการ C ขนาดใหญ่มักใช้ภาษาสคริปต์ในการสร้างรหัสซ้ำระหว่างการสร้าง


1
เราสามารถสร้างรหัส C ซ้ำ ๆ โดยใช้ภาษา / เครื่องมือ templating ภายนอกหรือเชลล์สคริปต์หรือใช้ ant หรือ ...
งาน

อะไรคือ URL รัสเซียที่ Google ทำให้งงงวยที่คุณได้ระบุไว้ในคำตอบของคุณ การคลิกที่มันทำให้พรอมต์ "เปิด" ปรากฏขึ้นในเบราว์เซอร์ของฉัน ปลอดภัยไหม
Robert Harvey

2
@RobertHarvey สแกนไวรัสและความปลอดภัยของฉันคิดว่ามันดี เป็นไฟล์ส่วนหัวภาษา C, นามสกุล. h มันปรากฏขึ้นจริง
maple_shaft

@maple_shaft: ตกลง ฉันได้ลบ Googlization ไปที่ลิงก์แล้ว
Robert Harvey

มันเป็นแค่ไฟล์ข้อความ. h (ส่วนหัว C)
Charles Salvia

15

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

  1. เขียนสำหรับสิ่งที่จำเป็น Generality สามารถมาภายหลังหากจำเป็น
  2. หากจำเป็นต้องใช้ทั่วไปพอยน์เตอร์พอยน์เตอร์พอยน์เตอร์ฟังก์ชั่นและขนาดโครงสร้างสามารถประเมินค่าได้ ยกตัวอย่างเช่น qsort () ประจำใช้งานทั้งสามอย่าง
  3. ให้น้ำหนักกับการทำให้รหัสของคุณชัดเจน

5

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

เมื่อเขียนโซลูชันทั่วไปสำหรับผู้ชมทั่วไปคุณต้องพิจารณา:

  1. ผู้บริโภคอาจใช้วิธีแปลกใหม่ในการใช้รหัสของคุณอย่างไรและคุณจะช่วยเหลือพวกเขาได้อย่างไร
  2. ฉันต้องจับข้อผิดพลาดอะไรบ้าง ฉันไม่ควรตรวจจับข้อผิดพลาดอะไรบ้าง
  3. API จำเป็นต้องมีความแข็งแกร่งเพียงใด ฉันต้องมีการโอเวอร์โหลดจำนวนเท่าใด
  4. ฉันต้องมีมาตรการรักษาความปลอดภัยใดเพื่อที่จะไม่สามารถใช้รหัสเพื่อวัตถุประสงค์ที่ชั่วร้ายได้

Eric Lippert เขียนบทความทั้งหมดเกี่ยวกับการตัดสินใจออกแบบเพียงครั้งเดียวในรหัสกรอบงาน. NET สรุปแล้วมันมักจะง่ายกว่าที่จะปรับโครงสร้าง


3

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


2
"สิ่งที่ถือว่าเป็น 'ยุ่ง' ในภาษาอื่นเป็นเพียงแบบทั่วไป C. โดยปกติแล้วพอมันจะดูเป็นธรรมชาติมากขึ้นเมื่อคุณได้รับประสบการณ์มากขึ้นในภาษา" ฮ่า ๆ ๆ ๆ ๆ ! C เป็นหนึ่งในภาษาที่ฉันชอบมาตลอด แต่ก็ยังตลกดีอยู่
GlenPeterson

0

ปัญหาการนำมาใช้ซ้ำของคุณนั้นไม่เหมือนกันสำหรับ C. อย่างไรก็ตามการใช้งาน HashMap ที่ยอดเยี่ยมและนำกลับมาใช้ซ้ำได้บางอย่างนั้นอยู่ที่นั่นซึ่งคุณสามารถนำมาใช้เป็นฐานได้ สิ่งหนึ่งที่เคยมีมาประมาณสิบปีหรือสองปีอยู่ใน Java: http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

คอลเลกชัน Java ทั้งหมดรวมถึง HashMap กำลังเขียนใหม่สำหรับ Java 8 เพื่อกำจัดแนวคิดของตัววนซ้ำและคุณต้องการให้ส่งผ่านฟังก์ชันไปยังคอลเลกชันเพื่อให้สามารถวนซ้ำภายในได้ นี่เป็นชัยชนะครั้งใหญ่สำหรับการเขียนโค้ดที่เกิดขึ้นพร้อมกัน - คอลเลกชันสามารถจัดการการทำงานพร้อมกันภายในเพื่อให้ลูกค้าไม่จำเป็นต้อง ที่กล่าวว่าโปรแกรมเมอร์ส่วนใหญ่ไม่ได้ใช้ในการผ่านตัวชี้ฟังก์ชั่น นี่เป็นงานที่ดีที่อธิบายว่าที่ใดที่ HashMap มุ่งหน้าไปที่: http://cr.openjdk.java.net/~briangoetz/lambda/collections-overview.html

ไวยากรณ์ของ Scala อาจทำให้ยากที่จะแปลบางอย่างเช่นนี้เป็น C แต่นี่เป็นตัวอย่างของคอลเล็กชันที่ทำงานในแบบที่ Java 8 collection จะไม่เปลี่ยนรูปโดยปริยายโดยให้วิธีการเปลี่ยนรูปแบบต่างๆโดยใช้พอยน์เตอร์ของฟังก์ชันและอื่น ๆ สิ่งดีๆ ๆ เช่นแอปพลิเคชั่นบางส่วน: http://www.scala-lang.org/api/current/index.html#scala.collection.Map

หากคุณกำลังทำสิ่งต่าง ๆ มากมายด้วย HashMaps และโครงสร้างข้อมูลขั้นสูงอื่น ๆ คุณอาจต้องการดูภาษาที่รองรับ

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

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


1
- "ฉันไม่คิดว่ามันเป็นการง่ายเกินไปที่จะพูดว่าเมื่อใดก็ตามที่คุณพบว่าคุณทำรหัสซ้ำคุณควรสร้างฟังก์ชั่น" - ใน C ที่เป็นการทำให้เข้าใจง่ายเกินไป ลองสร้าง Stack ใน C เพื่อหาจำนวนเต็มคู่และสายอักขระโดยไม่ทำซ้ำฟังก์ชัน push และ pop สำหรับแต่ละประเภทข้อมูล
mike30

@ ไมค์ไม่สามารถใช้พอยน์เตอร์พอยน์เตอร์ได้ใช่ไหม?
sakisk

@ ไมค์ - จุดดี แต่จุดกลับกันก็คือคุณสามารถใช้สแต็กสำหรับแต่ละชนิดข้อมูลในลักษณะที่คุณไม่จำเป็นต้องใช้สแต็กอีกครั้ง ดังนั้นเป้าหมายไม่ใช่ numberOfImplementations = 1 แต่ชอบมากกว่า 5 คุณอาจสามารถทำให้intการนำไปใช้งานเป็นไปcharได้ ... มันนานมากแล้วตั้งแต่ฉันใช้ C ที่ฉันจำไม่ได้
GlenPeterson

1
@faif ใช่ แต่ความจำเป็นในการใช้โมฆะพอยน์เตอร์ทำให้จุดสำหรับ C. พอยน์เตอร์ชี้เป็นช้าและสวยงามกว่าทางเลือกที่พบใน C ++ การทำซ้ำรหัสจะค่อนข้างสูงในรายการตัวเลือกที่เป็นไปได้เมื่อทำการเข้ารหัสใน C.
mike30

@ mike30 มีหลักฐาน / การอ้างอิงเกี่ยวกับ: "การชี้พอยน์เตอร์ใน C ช้ากว่าทางเลือกอื่นใน C ++ หรือไม่"
sakisk

0

ฉันสงสัยว่าการทำสำเนาโค้ดเป็นสิ่งที่จำเป็นหรือไม่เมื่อพูดถึงการเขียนโครงสร้างข้อมูลทั่วไปและ 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 ที่มีประสิทธิภาพเพียง ints32 และฉันพบว่าในกรณีการใช้งานของฉันอย่างน้อย (อาจเป็นโดเมนของฉันตั้งแต่ฉันทำงานในพื้นที่เช่น raytracing, การประมวลผลตาข่าย, การประมวลผลภาพ, ระบบอนุภาค, ผูกพันกับภาษาสคริปต์, การใช้งานชุด GUI แบบมัลติเธรดต่ำ ฯลฯ - สิ่งต่าง ๆ ในระดับต่ำ แต่ไม่ใช่ระดับต่ำเป็นระบบปฏิบัติการ) รหัสนั้นเกิดขึ้นโดยบังเอิญเพื่อให้มีประสิทธิภาพมากขึ้นและง่ายขึ้นเพียงแค่จัดเก็บดัชนีไปยังสิ่งต่าง ๆ เช่นนี้ มันทำให้ฉันมักจะทำงานพูด 75% ของเวลาด้วยความยุติธรรมint32_tและfloat32 ในโครงสร้างข้อมูลที่ไม่สำคัญของฉันหรือเพียงแค่จัดเก็บสิ่งต่าง ๆ ที่มีขนาดเท่ากัน (เกือบตลอดเวลา 32 บิต)

และโดยธรรมชาติหากเหมาะสมกับกรณีของคุณคุณสามารถหลีกเลี่ยงการใช้งานโครงสร้างข้อมูลจำนวนมากสำหรับประเภทข้อมูลที่แตกต่างกันเนื่องจากคุณจะทำงานกับผู้ใช้จำนวนน้อยในตอนแรก

การทดสอบและความน่าเชื่อถือ

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

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

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

การทำสำเนารหัสสำหรับ Minimalism?

นี่เป็นความคิดที่ตลกที่โผล่ขึ้นมาในหัวของฉัน แต่ลองพิจารณากรณีที่เราอาจพบห้องสมุด C และ C ++ ซึ่งทำสิ่งเดียวกันโดยประมาณ: ทั้งสองมีฟังก์ชั่นเดียวกันโดยประมาณการจัดการข้อผิดพลาดจำนวนเท่ากัน มีประสิทธิภาพมากกว่าที่อื่น ๆ และที่สำคัญที่สุดทั้งสองอย่างนั้นถูกนำไปใช้อย่างมีประสิทธิภาพทดสอบอย่างดีและเชื่อถือได้ น่าเสียดายที่ฉันต้องพูดอย่างสมมุติที่นี่เพราะฉันไม่เคยพบอะไรที่ใกล้เคียงกับการเปรียบเทียบแบบเคียงข้างกัน แต่สิ่งที่ใกล้เคียงที่สุดที่ฉันเคยพบในการเปรียบเทียบแบบเคียงข้างกันนี้มักจะมีไลบรารี C มากน้อยกว่า C ++ เทียบเท่า (บางครั้ง 1 / 10th ของขนาดรหัสของมัน)

และฉันเชื่อว่าเหตุผลนั้นเป็นเพราะอีกครั้งเพื่อแก้ปัญหาด้วยวิธีทั่วไปที่จัดการกรณีการใช้ที่หลากหลายที่สุดแทนที่จะเป็นกรณีการใช้งานที่แน่นอนหนึ่งกรณีอาจต้องใช้โค้ดหลายร้อยถึงพันบรรทัดในขณะที่หลังอาจต้องการเพียง หนึ่งโหล. ทั้งๆที่มีความซ้ำซ้อนและทั้งๆที่ห้องสมุดมาตรฐาน C เป็นสุดยอดเมื่อมันมาถึงการให้โครงสร้างข้อมูลมาตรฐานมันมักจะจบลงด้วยการผลิตรหัสน้อยลงในมือมนุษย์เพื่อแก้ปัญหาเดียวกันและฉันคิดว่ามันเป็นเพราะ เพื่อความแตกต่างในแนวโน้มของมนุษย์ระหว่างสองภาษานี้ หนึ่งส่งเสริมการแก้ปัญหากับกรณีการใช้งานที่เฉพาะเจาะจงอื่น ๆ มีแนวโน้มที่จะส่งเสริมการแก้ปัญหาที่เป็นนามธรรมและทั่วไปมากขึ้นกับกรณีการใช้ที่หลากหลายที่สุด แต่ผลลัพธ์สุดท้ายของสิ่งเหล่านี้ไม่ได้

ฉันกำลังดู raytracer ของใครบางคนใน GitHub วันอื่น ๆ และมันถูกนำมาใช้ใน C ++ และจำเป็นต้องใช้รหัสมากสำหรับของเล่น raytracer และฉันไม่ได้ใช้เวลามากในการดูโค้ด แต่มีโครงสร้างของวัตถุประสงค์ทั่วไปในเรือที่มีวิธีการจัดการมากกว่าวิธีที่ raytracer ต้องการ และฉันรู้ว่ารูปแบบของการเข้ารหัสนั้นเพราะฉันเคยใช้ C ++ แบบเดียวกันในรูปแบบสุดยอดนิยมโดยมุ่งเน้นที่การสร้างไลบรารี่แบบเต็มรูปแบบของโครงสร้างข้อมูลที่มีวัตถุประสงค์ทั่วไปเป็นอย่างแรก ปัญหาที่มือแล้วจัดการปัญหาที่แท้จริงที่สอง แต่ในขณะที่โครงสร้างทั่วไปเหล่านั้นอาจกำจัดความซ้ำซ้อนบางอย่างที่นี่และที่นั่นและเพลิดเพลินกับการใช้ซ้ำในบริบทใหม่ ในการแลกเปลี่ยนพวกเขาทำให้โครงการขยายตัวอย่างมหาศาลโดยการแลกเปลี่ยนความซ้ำซ้อนเล็กน้อยด้วยการโหลดโค้ด / ฟังก์ชั่นที่ไม่จำเป็นและจำนวนมากไม่จำเป็นต้องบำรุงรักษาง่ายกว่าในอดีต ในทางตรงกันข้ามฉันมักจะพบว่ามันยากกว่าที่จะรักษาเพราะมันยากที่จะรักษาการออกแบบของสิ่งทั่วไปที่มีการตัดสินใจออกแบบสมดุลสมดุลกับความต้องการที่กว้างที่สุดเท่าที่จะทำได้

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