สิ่งที่ควรและสิ่งที่ไม่ควรอยู่ในไฟล์ส่วนหัว? [ปิด]


71

สิ่งที่ไม่ควรรวมอยู่ในไฟล์ส่วนหัวอย่างแน่นอน?

ตัวอย่างเช่นถ้าฉันทำงานกับรูปแบบมาตรฐานอุตสาหกรรมที่มีค่าคงที่จำนวนมากเป็นวิธีปฏิบัติที่ดีที่จะกำหนดไว้ในไฟล์ส่วนหัว (ถ้าฉันเขียนโปรแกรมแยกวิเคราะห์สำหรับรูปแบบนั้น)

ฟังก์ชั่นใดที่ควรเข้าไปในไฟล์ส่วนหัว?
ฟังก์ชั่นอะไรไม่ควร?


1
สั้นและไม่เจ็บปวด: คำจำกัดความและส่วนที่ต้องการในโมดูลมากกว่าหนึ่ง
ott--

21
การทำเครื่องหมายคำถามนี้ว่า "กว้างเกินไป" และการปิดเป็นความอัปยศอดสูที่เกินความจำเป็น คำถามนี้ว่าขอให้สิ่งที่ฉันกำลังมองหา - คำถามจะเกิดขึ้นได้ดีและไม่ถามคำถามที่ชัดเจนมาก: สิ่งที่เป็นวิธีปฏิบัติที่ดีที่สุด? หากนี่คือ "กว้างเกินไป" สำหรับซอฟต์แวร์ด้านวิศวกรรม .. เราก็สามารถปิดฟอรัมนี้ทั้งหมดได้เช่นกัน
เริ่ม

TL; DR สำหรับ C ++ ในรุ่นที่สี่ของ "ภาษาการเขียนโปรแกรม C ++" ที่เขียนโดย Bjarne Stroustrup (ผู้สร้าง) ในส่วนที่ 15.2.2 มีการอธิบายว่าส่วนหัวควรมีอะไรและไม่ควรมี ฉันรู้ว่าคุณติดแท็กคำถามไว้ที่ C แต่คำแนะนำบางอย่างก็มีผลเช่นกัน ผมคิดว่านี่เป็นคำถามที่ดี ...
horro

คำตอบ:


57

สิ่งที่จะใส่ในส่วนหัว:

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

สิ่งที่ไม่ได้อยู่ในส่วนหัว:

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

อะไรคือชุดของ#includeข้อความที่น้อยที่สุด?

สิ่งนี้กลายเป็นคำถามที่ไม่สำคัญ คำจำกัดความ TL; DR: ไฟล์ส่วนหัวจะต้องมีไฟล์ส่วนหัวที่กำหนดประเภทแต่ละประเภทที่ใช้โดยตรงในหรือโดยตรงประกาศแต่ละฟังก์ชั่นที่ใช้ในไฟล์ส่วนหัวที่มีปัญหา แต่ต้องไม่รวมอะไร ประเภทการอ้างอิงตัวชี้หรือ C ++ ไม่ได้รับสิทธิ์เป็นการใช้โดยตรง การอ้างอิงไปข้างหน้าเป็นที่ต้องการ

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

#include "path/to/random/header_under_test"
int main () { return 0; }

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


ดังนั้นถ้าฉันมีห้องสมุดที่กำหนดโครงสร้างเรียกว่า A และห้องสมุดนี้เรียกว่า B ใช้ struct นั้นและไลบรารี B ถูกใช้โดยโปรแกรม C ฉันควรรวมไฟล์ส่วนหัวของไลบรารี A ไว้ในส่วนหัวหลักของไลบรารี B หรือควร ฉันแค่ส่งต่อประกาศหรือไม่ library A รวบรวมและเชื่อมโยงกับ Library B ระหว่างการรวบรวม
MarcusJ

@ MarcusJ - สิ่งแรกที่ฉันแสดงรายการภายใต้สิ่งที่ไม่ได้อยู่ในส่วนหัวคือข้อความ #include ฟรี หากไฟล์ส่วนหัว B ไม่ได้ขึ้นอยู่กับคำจำกัดความในไฟล์ส่วนหัว A อย่ารวม # ไฟล์ส่วนหัว A ในไฟล์ส่วนหัว B ไฟล์ส่วนหัวไม่ใช่สถานที่ที่จะระบุบุคคลที่สามหรือคำแนะนำในการสร้าง สิ่งเหล่านี้ไปที่อื่นเช่นไฟล์ readme ระดับบนสุด
David Hammen

1
@MarcusJ - ฉันอัปเดตคำตอบเพื่อพยายามตอบคำถามของคุณ โปรดทราบว่าไม่มีคำตอบสำหรับคำถามของคุณ ฉันจะอธิบายด้วยสุดขั้วสองสามอย่าง กรณีที่ 1: ที่เดียวที่ไลบรารี B ใช้ฟังก์ชันการทำงานของไลบรารี A โดยตรงในไฟล์ต้นฉบับไลบรารี B กรณีที่ 2: Library B เป็นส่วนขยายบางส่วนของฟังก์ชันในไลบรารี A โดยมีไฟล์ส่วนหัวสำหรับไลบรารี B โดยตรงโดยใช้ประเภทและ / หรือฟังก์ชั่นที่กำหนดไว้ในไลบรารี A ในกรณีที่ 1 ไม่มีเหตุผลที่จะเปิดเผยไลบรารี A ใน ส่วนหัวสำหรับห้องสมุด B ในกรณีที่ 2 การเปิดรับนี้ค่อนข้างบังคับ
David Hammen

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

การเพิ่มค่าคงที่ให้กับไฟล์ส่วนหัวเป็นเรื่องใหญ่หรือไม่?
mding5692

15

นอกจากสิ่งที่ได้กล่าวไปแล้ว

ไฟล์ H ควรมี:

  • เอกสารรหัสที่มา !!! อย่างน้อยที่สุดจุดประสงค์ของพารามิเตอร์ต่าง ๆ และค่าตอบแทนของฟังก์ชั่นคืออะไร
  • Header guards, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

ไฟล์ H ไม่ควรมี:

  • การจัดสรรข้อมูลทุกรูปแบบ
  • นิยามฟังก์ชั่น ฟังก์ชั่นอินไลน์อาจเป็นข้อยกเว้นที่เกิดขึ้นได้ยากในบางกรณี
  • staticสิ่งใดที่มีข้อความ
  • Typedefs, #defines หรือค่าคงที่ที่ไม่เกี่ยวข้องกับส่วนที่เหลือของแอปพลิเคชัน

(ฉันจะบอกว่าไม่มีเหตุผลใด ๆ ที่จะใช้ตัวแปร global / extern ที่ไม่คงที่ไม่ว่าที่ใดก็ตาม แต่นั่นเป็นการสนทนาสำหรับโพสต์อื่น)


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

5
@ Smartiert ฉันยังเป็นโรงเรียน "ปล่อยให้รหัสพูดเอง" แต่อย่างน้อยที่สุดคุณควรบันทึกการทำงานของคุณแม้ว่าจะไม่มีใครนอกจากตัวคุณเองก็จะใช้มัน สิ่งที่น่าสนใจเป็นพิเศษ: ในกรณีที่ฟังก์ชั่นมีการจัดการข้อผิดพลาดรหัสข้อผิดพลาดอะไรที่ส่งคืนและภายใต้เงื่อนไขที่ล้มเหลว? จะเกิดอะไรขึ้นกับพารามิเตอร์ (บัฟเฟอร์ตัวชี้ ฯลฯ ) หากฟังก์ชันล้มเหลว อีกสิ่งหนึ่งที่มีความเกี่ยวข้องมากคือ: พารามิเตอร์ตัวชี้ส่งคืนสิ่งที่ผู้เรียกเช่นพวกเขาคาดหวังว่าหน่วยความจำที่จัดสรร? ->

1
ควรชัดเจนกับผู้เรียกว่าการจัดการข้อผิดพลาดเกิดขึ้นภายในฟังก์ชันและสิ่งใดที่ไม่ได้ทำ หากฟังก์ชั่นนี้คาดว่าจะมีบัฟเฟอร์ที่ถูกจัดสรรก็น่าจะปล่อยให้การตรวจสอบนอกขอบเขตไปยังผู้โทรได้เช่นกัน หากฟังก์ชั่นนั้นใช้ฟังก์ชั่นอื่นที่จะดำเนินการจะต้องมีการบันทึกไว้ (เช่นเรียกใช้ link_list_init () ก่อน link_list_add ()) และในที่สุดหากฟังก์ชั่นมี "ผลข้างเคียง" เช่นการสร้างไฟล์, เธรด, ตัวจับเวลาหรืออะไรก็ตามมันควรจะระบุไว้ในเอกสารประกอบ ->

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

2
บิตล่าช้า แต่ +1 สำหรับเอกสาร ทำไมคลาสนี้ถึงมีอยู่จริง? รหัสไม่พูดเพื่อตัวเอง ฟังก์ชั่นนี้ทำอะไร? RTFC (อ่านไฟล์. cpp ละเอียด) เป็นตัวย่อหยาบคายสี่ตัวอักษร หนึ่งไม่ควรต้อง RTFC เพื่อความเข้าใจ ต้นแบบในส่วนหัวควรสรุปในข้อคิดเห็นบางส่วนที่แยกได้ (เช่น doxygen) สิ่งที่ขัดแย้งคืออะไรและฟังก์ชั่นทำอะไร ทำไมสมาชิกข้อมูลนี้จึงมีอยู่ประกอบด้วยอะไรและมีค่าเป็นเมตรฟุตหรือยาว? นั่นก็เป็นอีกเรื่องสำหรับความคิดเห็น (สกัดได้) ที่มีอยู่
David Hammen

4

ฉันอาจจะไม่เคยบอกว่าเคย แต่งบที่สร้างข้อมูลและรหัสตามที่ได้รับการแยกวิเคราะห์ไม่ควรอยู่ในไฟล์. h

แมโครฟังก์ชันอินไลน์และเทมเพลตอาจดูเหมือนข้อมูลหรือรหัส แต่จะไม่สร้างรหัสตามที่มีการแยกวิเคราะห์ แต่จะใช้เมื่อใช้แทน รายการเหล่านี้มักจะต้องใช้มากกว่า. c หรือ. cpp มากกว่าหนึ่งรายการดังนั้นจึงอยู่ใน. h

ในมุมมองของฉันไฟล์ส่วนหัวควรมีอินเทอร์เฟซสำหรับการใช้งานขั้นต่ำที่สอดคล้องกับ. c หรือ. cpp อินเทอร์เฟซสามารถรวม #defines คลาส typedef คำจำกัดความของโครงสร้างต้นแบบของฟังก์ชันและคำจำกัดความ extern ที่ต้องการน้อยกว่าสำหรับตัวแปรส่วนกลาง อย่างไรก็ตามหากมีการใช้การประกาศในไฟล์ต้นฉบับเพียงไฟล์เดียวมันอาจจะถูกแยกออกจาก. h และอยู่ในไฟล์ต้นฉบับแทน

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


3

ไฟล์ส่วนหัวควรมีองค์กรต่อไปนี้:

  • ประเภทและคำจำกัดความคงที่
  • การประกาศวัตถุภายนอก
  • ประกาศฟังก์ชั่นภายนอก

ไฟล์ส่วนหัวไม่ควรมีคำจำกัดความของวัตถุเพียงพิมพ์คำจำกัดความและการประกาศวัตถุ


นิยามฟังก์ชั่นอินไลน์คืออะไร
Kos

หากฟังก์ชั่นแบบอินไลน์เป็นฟังก์ชั่น“ ผู้ช่วย” ที่ใช้ภายในโมดูล C เพียงโมดูลเดียวให้ใส่ไว้ในไฟล์. c เท่านั้น หากฟังก์ชั่นแบบอินไลน์ต้องสามารถมองเห็นได้ตั้งแต่สองโมดูลขึ้นไปวางไว้ในไฟล์ส่วนหัว
TheD

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

@ DonalFellows: นั่นเป็นวิธีการแก้แบ็คแฮนด์ กฎที่ดีกว่า: อย่าวางสิ่งของไว้ในส่วนหัวที่อาจมีการเปลี่ยนแปลงบ่อย ไม่มีอะไรผิดปกติกับการฝังฟังก์ชั่นเล็ก ๆ น้อย ๆ สั้น ๆ ในส่วนหัวถ้าฟังก์ชั่นไม่มี fanout และมีคำจำกัดความที่ชัดเจนว่าจะเปลี่ยนเฉพาะในกรณีที่โครงสร้างข้อมูลพื้นฐานเปลี่ยนแปลง ถ้าคำจำกัดความของฟังก์ชั่นเปลี่ยนเพราะนิยามโครงสร้างพื้นฐานเปลี่ยนไปใช่คุณต้องคอมไพล์ใหม่ทุกอย่าง แต่คุณจะต้องทำอย่างนั้นต่อไปเพราะนิยามโครงสร้างเปลี่ยนไป
David Hammen

0

งบที่สร้างข้อมูลและรหัสตามที่มีการแยกวิเคราะห์ไม่ควรอยู่ใน.hไฟล์ เท่าที่เป็นมุมมองของฉันเป็นห่วงไฟล์ส่วนหัวควรมีเพียงอินเตอร์เฟซการปฏิบัติขั้นต่ำไปยังที่สอดคล้องกันหรือ.c.cpp

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