ประโยชน์ของไลบรารีส่วนหัวเท่านั้น


101

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


เทมเพลตส่วนใหญ่ แต่จะช่วยให้แจกจ่ายและใช้งานได้ง่ายขึ้นเล็กน้อย
BoBTFish

4
ฉันต้องการเพิ่มข้อเสียของไลบรารีส่วนหัวเท่านั้นในขอบเขตของคำถาม ...
moooeeeep

มีข้อเสียอะไรบ้างที่ยังไม่ได้กล่าวถึง?
NebulaFox

7
@moooeeeep: สำหรับข้อเสียคุณอาจต้องการอ่านย่อหน้า"Stop inlining code"ในหน้าเว็บC ++ Dos และ Don'ts Chromium Projects
Mr.C64

คำตอบ:


57

มีสถานการณ์ที่ไลบรารีเฉพาะส่วนหัวเป็นตัวเลือกเดียวตัวอย่างเช่นเมื่อจัดการกับเทมเพลต

การมีไลบรารีเฉพาะส่วนหัวทำให้คุณไม่ต้องกังวลเกี่ยวกับแพลตฟอร์มต่างๆที่อาจใช้ไลบรารี เมื่อคุณแยกการนำไปใช้งานคุณมักจะทำเช่นนั้นเพื่อซ่อนรายละเอียดการใช้งานและแจกจ่ายไลบรารีเป็นชุดส่วนหัวและไลบรารี ( lib, dllหรือ.soไฟล์) แน่นอนว่าต้องรวบรวมสำหรับระบบปฏิบัติการ / เวอร์ชันต่างๆที่คุณให้การสนับสนุน

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

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


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

1
ฉันเพิ่งถามคำถามที่คล้ายกันเกี่ยวกับประโยชน์ด้านประสิทธิภาพของส่วนหัวเท่านั้น อย่างที่คุณเห็นไม่มีความแตกต่างในขนาดรหัส อย่างไรก็ตามการใช้งานส่วนหัวเท่านั้นที่เป็นตัวอย่างทำงานช้าลง 7% stackoverflow.com/questions/12290639/…
Homer6

@ Homer6 ขอบคุณที่ส่ง Ping มาให้ฉัน ฉันไม่เคยวัดสิ่งนี้จริง
Luchian Grigore

1
@LuchianGrigore ฉันไม่พบใครที่มีเช่นกัน นั่นเป็นเหตุผลว่าทำไมจึงต้องใช้เวลาสักครู่ในการตอบ มีความคิดเห็นเกี่ยวกับ "การเพิ่มขนาดโค้ด" และ "การใช้หน่วยความจำ" ที่เป็นการคาดเดามากมาย ในที่สุดฉันก็มีภาพรวมของความแตกต่างแม้ว่าจะเป็นเพียงตัวอย่างเดียวก็ตาม
Homer6

@ โฮเมอร์ 6. ทำไมจึงไม่เพิ่มขนาดโค้ด สมมติว่าคุณสร้าง libs หลายตัวที่ใช้เฉพาะส่วนหัว lib จากนั้นแอพของคุณจะใช้ libs ทั้งหมดที่คุณจะต้องมีหลายสำเนาแทนที่จะเชื่อมโยงกับไลบรารีที่ใช้ร่วมกันเดียว
pooya13

61

ประโยชน์ของไลบรารีส่วนหัวเท่านั้น:

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

ข้อเสียของไลบรารีส่วนหัวเท่านั้น:

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

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

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

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


22
ประเด็นสุดท้ายไม่สมเหตุสมผลจริงๆ เอกสารที่เหมาะสมใด ๆ จะรวมถึงการประกาศฟังก์ชันพารามิเตอร์ค่าส่งคืน ฯลฯ และความคิดเห็นที่เกี่ยวข้องทั้งหมด หากคุณต้องอ้างถึงไฟล์ส่วนหัวเอกสารนั้นล้มเหลว
Thomas

6
@ โทมัส - แม้จะมีห้องสมุดระดับมืออาชีพที่ดีที่สุด แต่บ่อยครั้งฉันก็พบว่าตัวเองต้องหันไปอ่านหัวข้อ "ดี" ในความเป็นจริงถ้าเอกสารที่เรียกว่า "ดี" ถูกดึงออกมาจากโค้ดพร้อมคำบรรยายฉันมักจะชอบอ่านส่วนหัว รหัสบวกความคิดเห็นบอกฉันได้มากกว่าเอกสารที่สร้างขึ้นโดยอัตโนมัติ
David Hammen

2
จุดสุดท้ายไม่ถูกต้อง ส่วนหัวเต็มไปด้วยรายละเอียดการใช้งานในสมาชิกส่วนตัวแล้วดังนั้นจึงไม่เหมือนกับไฟล์ cpp ที่ซ่อนรายละเอียดการใช้งานทั้งหมด นอกจากนี้ภาษาเช่น C # เป็นภาษา "เฉพาะส่วนหัว" ตามการออกแบบและ IDE จะดูแลรายละเอียดที่ไม่ชัดเจน ("พับ" ลง)
Mark Lakata

2
@ โทมัส: เห็นด้วยประเด็นสุดท้ายคือการหลอกลวงอย่างสมบูรณ์ คุณสามารถเก็บอินเทอร์เฟซและการใช้งานได้อย่างง่ายดายเช่นเดียวกับไลบรารีส่วนหัวเท่านั้น คุณเพียงแค่มีส่วนหัวของอินเทอร์เฟซ # รวมรายละเอียดการใช้งาน นี่คือเหตุผลที่ห้องสมุด Boost มักจะรวมถึงไดเรกทอรีย่อย (และ namespace) detailที่เรียกว่า
Nemo

4
@ โทมัส: ฉันไม่เห็นด้วย โดยทั่วไปไฟล์ส่วนหัวเป็นที่แรกที่ฉันไปเพื่อจัดทำเอกสาร หากส่วนหัวเขียนได้ดีมักไม่จำเป็นต้องมีเอกสารภายนอก
Joel Cornett

15

ฉันรู้ว่านี่เป็นกระทู้เก่า แต่ไม่มีใครพูดถึงอินเทอร์เฟซ ABI หรือปัญหาเฉพาะของคอมไพเลอร์ ฉันก็เลยคิดว่าฉันจะ

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

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

ตัวอย่างเช่นผู้จำหน่ายคอมไพเลอร์มักเปลี่ยนการนำ STL ไปใช้ระหว่างเวอร์ชัน หากคุณมีฟังก์ชันในไลบรารีที่ยอมรับ std :: vector ก็คาดว่าไบต์ในคลาสนั้นจะถูกจัดเรียงตามวิธีการจัดเรียงเมื่อไลบรารีถูกคอมไพล์ หากในคอมไพเลอร์เวอร์ชันใหม่ผู้ขายได้ทำการปรับปรุงประสิทธิภาพให้กับ std :: vector แล้วโค้ดของผู้ใช้จะเห็นคลาสใหม่ซึ่งอาจมีโครงสร้างที่แตกต่างกันและส่งผ่านโครงสร้างใหม่นั้นไปยังไลบรารีของคุณ ทุกอย่างลงเนินจากที่นั่น ... นี่คือเหตุผลที่ไม่แนะนำให้ส่งวัตถุ STL ข้ามขอบเขตไลบรารี เช่นเดียวกับประเภท C Run-Time (CRT)

ในขณะที่พูดถึง CRT โดยทั่วไปแล้วไลบรารีของคุณและซอร์สโค้ดของผู้ใช้จะต้องเชื่อมโยงกับ CRT เดียวกัน ด้วย Visual Studio หากคุณสร้างไลบรารีของคุณโดยใช้ CRT แบบมัลติเธรด แต่ผู้ใช้เชื่อมโยงกับ Multithreaded Debug CRT คุณจะมีปัญหาในการเชื่อมโยงเนื่องจากไลบรารีของคุณอาจไม่พบสัญลักษณ์ที่ต้องการ ฉันจำไม่ได้ว่าเป็นฟังก์ชันใด แต่สำหรับ Visual Studio 2015 Microsoft ได้สร้างฟังก์ชัน CRT แบบอินไลน์หนึ่งฟังก์ชัน ทันใดนั้นมันอยู่ในส่วนหัวไม่ใช่ไลบรารี CRT ดังนั้นไลบรารีที่คาดว่าจะพบในเวลาลิงก์ไม่สามารถทำได้อีกต่อไปและเกิดข้อผิดพลาดในการเชื่อมโยง ผลลัพธ์ก็คือไลบรารีเหล่านี้จำเป็นต้องทำการคอมไพล์ใหม่ด้วย Visual Studio 2015

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

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

โดยพื้นฐานแล้วเพื่อความปลอดภัยสิ่งเดียวที่คุณสามารถข้ามผ่านขอบเขตไลบรารีได้ถูกสร้างขึ้นในประเภทและข้อมูลเก่าธรรมดา (POD) ตามหลักการแล้ว POD ใด ๆ ควรอยู่ในโครงสร้างที่กำหนดไว้ในส่วนหัวของคุณเองและอย่าพึ่งพาส่วนหัวของบุคคลที่สาม

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

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


8

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


0

การซับในสามารถทำได้โดย Link Time Optimization (LTO)

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

ตัวอย่างที่เป็นรูปธรรมเล็กน้อยนี้แสดงไว้ที่: การเพิ่มประสิทธิภาพเวลาลิงก์และแบบอินไลน์

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

อย่างไรก็ตาม LTO อาจมีข้อเสียของตัวเองเช่นกัน: มีเหตุผลอะไรที่ไม่ใช้การเพิ่มประสิทธิภาพเวลาเชื่อมโยง (LTO)?

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