เหตุใดฉันจึงไม่ควรรวมไฟล์ cpp และใช้ส่วนหัวแทน


147

ดังนั้นฉันจึงเสร็จสิ้นการมอบหมายการเขียนโปรแกรม C ++ ครั้งแรกและได้รับเกรดของฉัน including cpp files instead of compiling and linking themแต่ตามการจัดลำดับที่ฉันหายไปเครื่องหมายสำหรับ ฉันไม่ชัดเจนเกินไปในสิ่งที่หมายถึง

เมื่อมองกลับไปที่รหัสของฉันฉันเลือกที่จะไม่สร้างไฟล์ส่วนหัวสำหรับชั้นเรียนของฉัน แต่ทำทุกอย่างในไฟล์ cpp (ดูเหมือนว่าจะทำงานได้ดีโดยไม่มีไฟล์ส่วนหัว ... ) ฉันคาดเดาว่านักเรียนระดับประถมหมายความว่าฉันเขียน '#include "mycppfile.cpp";' ในบางไฟล์ของฉัน

เหตุผลของฉันสำหรับ#includeการรับไฟล์ cpp คือ: - ทุกสิ่งที่ควรจะเข้าไปในไฟล์ส่วนหัวนั้นอยู่ในไฟล์ cpp ของฉันดังนั้นฉันจึงแกล้งทำเป็นว่าเป็นไฟล์ส่วนหัว - ใน Monkey-see-monkey ทำแฟชั่นฉันเห็นว่าคนอื่น ๆ ไฟล์ส่วนหัวอยู่#includeในไฟล์ดังนั้นฉันจึงทำเช่นเดียวกันกับไฟล์ cpp ของฉัน

แล้วฉันทำอะไรผิดและทำไมมันถึงไม่ดี?


36
นี่เป็นคำถามที่ดีจริงๆ ฉันคาดหวังว่ามือใหม่ c ++ จำนวนมากจะได้รับความช่วยเหลือจากสิ่งนี้
Mia Clarke

คำตอบ:


174

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

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

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

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

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

ถ้าคุณไม่เปลี่ยนแปลงบางสิ่ง

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

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

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

อืมมม คุณอาจจะมีบางอย่างที่นั่น แจ้งให้เราทราบว่ามันมีผลกับคุณอย่างไร


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

@veol Search for Head ชุดหนังสือเล่มแรก - ฉันไม่รู้ว่าพวกเขามีเวอร์ชัน C ++ ไหม headfirstlabs.com
Amarghosh

2
นี่คือข้อความที่ดีที่สุดจนถึงปัจจุบันที่ฉันได้ยินหรือใคร่ครวญ Justin Case ผู้เริ่มต้นที่ประสบความสำเร็จบรรลุการตอกบัตรโครงการด้วยการกดแป้นหนึ่งล้านครั้งซึ่งยังไม่ได้ส่งและโครงการ "โครงการแรก" ที่น่ายกย่องที่เห็นแสงสว่างของแอปพลิเคชันในฐานผู้ใช้จริงได้ตระหนักถึงปัญหาจากการปิด ฟังดูคล้ายกับสถานะขั้นสูงของคำนิยามปัญหาดั้งเดิมของ OP ลบด้วย "รหัสนี้เกือบร้อยครั้งและไม่สามารถคิดได้ว่าจะทำอะไรให้เป็นโมฆะ (ไม่มีวัตถุ) vs null (เหมือนหลาน) โดยไม่ต้องใช้โปรแกรมโดยข้อยกเว้น"
นิโคลัสจอร์แดน

แน่นอนว่าสิ่งนี้ล้วน แต่แตกต่างกันสำหรับเทมเพลตเพราะคอมไพเลอร์ส่วนใหญ่ไม่สนับสนุน / ใช้งานคำหลัก 'ส่งออก'
KitsuneYMG

1
อีกประเด็นหนึ่งคือคุณมีห้องสมุดที่ทันสมัยหลายแห่ง (ถ้านึกถึง BOOST) ที่ใช้ส่วนหัวของชั้นเรียนเท่านั้น ... โฮเดี๋ยวก่อน? เหตุใดโปรแกรมเมอร์ที่มีประสบการณ์จึงไม่แยกส่วนต่อประสานจากการใช้งาน คำตอบส่วนหนึ่งอาจเป็นสิ่งที่คนตาบอดพูดอีกส่วนหนึ่งอาจเป็นได้ว่าไฟล์หนึ่งไฟล์ดีกว่าสองไฟล์เมื่อเป็นไปได้และอีกส่วนหนึ่งคือการเชื่อมโยงมีค่าใช้จ่ายสูงกว่าค่าที่ค่อนข้างสูง ฉันเคยเห็นโปรแกรมทำงานเร็วขึ้นสิบเท่าโดยมีการรวมแหล่งที่มาโดยตรงและคอมไพเลอร์ให้เกิดประโยชน์สูงสุด เนื่องจากการลิงก์ส่วนใหญ่บล็อกการเพิ่มประสิทธิภาพ
kriss

45

นี่อาจเป็นคำตอบที่ละเอียดกว่าที่คุณต้องการ แต่ฉันคิดว่าคำอธิบายที่เหมาะสมเป็นสิ่งที่ถูกต้อง

ใน C และ C ++ ไฟล์ต้นฉบับหนึ่งไฟล์จะถูกกำหนดเป็นหนึ่งหน่วยการแปลหน่วยการแปลตามแบบแผนไฟล์ส่วนหัวจะมีการประกาศฟังก์ชั่นคำจำกัดความประเภทและคำจำกัดความของคลาส การใช้งานฟังก์ชั่นที่แท้จริงอยู่ในหน่วยการแปลเช่นไฟล์. cpp

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

/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);

/* function body, or function definition. */
int add(int a, int b) 
{
   return a + b;
}

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

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

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

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

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

ข้อมูลสรุปอย่างย่อของกระบวนการทั้งหมดจากรหัส C ++ (หลายไฟล์) และไปสู่การปฏิบัติการขั้นสุดท้าย:

  • preprocessorจะดำเนินการซึ่งจะแยกวิเคราะห์คำสั่งทั้งหมดที่เริ่มต้นด้วย '#' คำสั่ง #include เชื่อมไฟล์ที่รวมเข้าด้วยกันให้เป็นรอง นอกจากนี้ยังทำการทดแทนมาโครและโทเค็นการวาง
  • คอมไพเลอร์จริงรันบนไฟล์ข้อความระดับกลางหลังจากระยะตัวประมวลผลล่วงหน้าและปล่อยรหัสแอสเซมเบลอร์
  • ประกอบวิ่งบนไฟล์ประกอบและส่งเสียงรหัสเครื่องนี้มักจะเรียกว่าไฟล์วัตถุและตามรูปแบบไบนารีของระบบการทำงานในคำถาม ตัวอย่างเช่น Windows ใช้ PE (รูปแบบที่ปฏิบัติการได้แบบพกพา) ในขณะที่ Linux ใช้รูปแบบ Unix System V ELF พร้อมส่วนขยาย GNU ในขั้นตอนนี้สัญลักษณ์ยังคงถูกทำเครื่องหมายว่าไม่ได้กำหนด
  • ในที่สุดlinkerจะทำงาน ขั้นตอนก่อนหน้าทั้งหมดถูกเรียกใช้ในหน่วยการแปลแต่ละหน่วยตามลำดับ อย่างไรก็ตามตัวเชื่อมโยงสเตจทำงานกับไฟล์อ็อบเจ็กต์ทั้งหมดที่สร้างขึ้นซึ่งแอสเซมเบลอร์สร้างขึ้น ตัวเชื่อมโยงจะแก้ไขสัญลักษณ์และทำเวทมนต์มากมายเช่นการสร้างส่วนและเซกเมนต์ซึ่งขึ้นอยู่กับแพลตฟอร์มเป้าหมายและรูปแบบไบนารี โปรแกรมเมอร์ไม่จำเป็นต้องรู้สิ่งนี้โดยทั่วไป แต่ก็มีประโยชน์ในบางกรณี

นี่เป็นสิ่งที่มากกว่าที่คุณร้องขออย่างแน่นอน แต่ฉันหวังว่ารายละเอียดที่เป็นประโยชน์จะช่วยให้คุณเห็นภาพที่ใหญ่ขึ้น


2
ขอบคุณสำหรับคำอธิบายอย่างละเอียด ฉันยอมรับมันยังไม่สมเหตุสมผลสำหรับฉันและฉันคิดว่าฉันจะต้องอ่านคำตอบของคุณอีกครั้ง (และอีกครั้ง)
ialm

1
+1 สำหรับคำอธิบายที่ยอดเยี่ยม มันแย่มากที่มันจะทำให้มือใหม่ออกจาก C ++ :)
goldPseudo

1
เฮ้อย่ารู้สึกผิด ๆ ใน Stack Overflow คำตอบที่ยาวที่สุดไม่ใช่คำตอบที่ดีที่สุด

int add(int, int);เป็นฟังก์ชั่นการประกาศ ต้นแบบint, intส่วนหนึ่งของมันเป็นเพียง อย่างไรก็ตามฟังก์ชั่นทั้งหมดใน C ++ นั้นมีต้นแบบดังนั้นคำนี้เหมาะสมสำหรับ C. ฉันได้แก้ไขคำตอบของคุณสำหรับเอฟเฟกต์นี้แล้ว
melpomene

exportสำหรับเทมเพลตได้ถูกลบออกจากภาษาในปี 2011 คอมไพเลอร์ไม่ได้รับการสนับสนุนจริงๆ
melpomene

10

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


6

คิดว่าไฟล์ cpp เป็นกล่องดำและไฟล์. h เป็นแนวทางในการใช้กล่องดำเหล่านั้น

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

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

นอกจากนี้ยังได้อ่านสิ่งนี้: ไฟล์ส่วนหัวรวมถึงรูปแบบ


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

นี่เป็นบทความที่ยอดเยี่ยม Veol นี่คือส่วนหัวที่คอมไพเลอร์ต้องการข้อมูลเกี่ยวกับขนาดของคลาส การประกาศไปข้างหน้าจะใช้เมื่อคุณใช้พอยน์เตอร์เท่านั้น
pankajt

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

6

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

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

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


5

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


3

หากคุณรวม # ไฟล์ cpp ในไฟล์อื่น ๆ ในโปรแกรมของคุณคอมไพเลอร์จะพยายามรวบรวมไฟล์ cpp หลายครั้งและจะสร้างข้อผิดพลาดเนื่องจากจะมีการใช้งานหลายวิธีเดียวกัน

การรวบรวมจะใช้เวลานานขึ้น (ซึ่งกลายเป็นปัญหาสำหรับโครงการขนาดใหญ่) หากคุณทำการแก้ไขในไฟล์ #included cpp ซึ่งจะบังคับให้คอมไพล์ไฟล์ใหม่อีกครั้ง

เพียงแค่ใส่การประกาศของคุณลงในไฟล์ส่วนหัวและรวมสิ่งเหล่านั้น (เพราะพวกเขาไม่ได้สร้างรหัสต่อ se) และ linker จะขอประกาศด้วยรหัส cpp ที่สอดคล้องกัน (ซึ่งจะได้รับรวบรวมเพียงครั้งเดียว)


ดังนั้นนอกเหนือจากการรวบรวมเวลานานฉันจะเริ่มมีปัญหาเมื่อฉันรวมไฟล์ cpp ในไฟล์ต่าง ๆ มากมายที่ใช้ฟังก์ชั่นในไฟล์ cpp ที่รวมมาด้วย
ialm

ใช่สิ่งนี้เรียกว่าการชนของเนมสเปซ สิ่งที่น่าสนใจคือการเชื่อมโยงกับ libs จะแนะนำปัญหาเนมสเปซหรือไม่ โดยทั่วไปแล้วฉันพบว่าคอมไพเลอร์สร้างช่วงเวลาการรวบรวมที่ดีขึ้นสำหรับขอบเขตหน่วยการแปล (ทั้งหมดในไฟล์เดียว) ซึ่งแนะนำปัญหาเนมสเปซ - ซึ่งนำไปสู่การแยกอีกครั้ง .... คุณสามารถรวมไฟล์รวมไว้ในแต่ละหน่วยการแปล มีแม้กระทั่ง pragma (#pragma หนึ่งครั้ง) ที่ควรจะบังคับใช้สิ่งนี้ แต่นั่นก็คือ supposition supposition ระวังอย่าใช้ libs (.O files) สุ่มสี่สุ่มห้าไม่ว่าที่ใดก็ตามเนื่องจากไม่มีการบังคับใช้ลิงก์ 32 บิต
นิโคลัสจอร์แดน

2

แม้ว่ามันจะเป็นไปได้ที่จะทำอย่างที่คุณทำ แต่การปฏิบัติมาตรฐานคือการใส่การประกาศที่ใช้ร่วมกันลงในไฟล์ส่วนหัว (.h) และคำจำกัดความของฟังก์ชั่นและตัวแปร - การนำไปใช้ - ลงในไฟล์ต้นฉบับ (.cpp)

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


2

การใช้งานสถาปัตยกรรมและการห่อหุ้มข้อมูลอีกครั้ง

นี่คือตัวอย่าง:

สมมติว่าคุณสร้างไฟล์ cpp ซึ่งมีรูปแบบของสตริงรูทีนทั้งหมดใน mystring คลาสคุณวาง class decl สำหรับสิ่งนี้ใน mystring.h การคอมไพล์ mystring.cpp เป็นไฟล์. obj

ตอนนี้ในโปรแกรมหลักของคุณ (เช่น main.cpp) คุณรวมส่วนหัวและลิงก์กับ mystring.obj เพื่อใช้ mystring ในโปรแกรมของคุณคุณไม่สนใจรายละเอียดว่า mystring ถูกนำไปใช้อย่างไรเนื่องจากส่วนหัวระบุว่ามันสามารถทำอะไรได้บ้าง

ตอนนี้ถ้าเพื่อนต้องการที่จะใช้คลาส mystring ของคุณคุณให้เขา mystring.h และ mystring.obj เขาก็ไม่จำเป็นต้องรู้ว่ามันใช้งานได้นานแค่ไหน

ในภายหลังหากคุณมีไฟล์. obj มากกว่านี้คุณสามารถรวมเป็นไฟล์. lib และเชื่อมโยงไปยังไฟล์นั้นแทน

คุณสามารถตัดสินใจที่จะเปลี่ยนไฟล์ mystring.cpp และนำไปใช้อย่างมีประสิทธิภาพมากขึ้นซึ่งจะไม่ส่งผลกระทบต่อ main.cpp หรือโปรแกรมเพื่อนของคุณ


2

ถ้ามันเหมาะกับคุณก็ไม่มีอะไรผิดปกติยกเว้นว่ามันจะทำให้ขนของคนที่คิดว่ามีวิธีเดียวเท่านั้นที่จะทำสิ่งต่าง ๆ

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

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

คำตอบจำนวนมากพูดถึงการแยกอินเทอร์เฟซจากการใช้งาน อย่างไรก็ตามนี่ไม่ใช่คุณลักษณะโดยรวมของไฟล์รวมและเป็นเรื่องปกติที่ #include "ส่วนหัว" ไฟล์ที่รวมการนำไปใช้โดยตรง (แม้แต่ C ++ Standard Library ก็สามารถทำได้ในระดับที่สำคัญ)

สิ่งเดียวที่แท้จริง "ไม่เป็นทางการ" เกี่ยวกับสิ่งที่คุณทำคือการตั้งชื่อไฟล์ที่รวม ".cpp" แทน ".h" หรือ ".hpp"


1

เมื่อคุณคอมไพล์และลิงก์โปรแกรมคอมไพเลอร์จะรวบรวมไฟล์ cpp แต่ละไฟล์ก่อนแล้วจึงทำการเชื่อมโยง (เชื่อมต่อ) ไฟล์เหล่านั้น ส่วนหัวจะไม่ถูกรวบรวมเว้นแต่จะรวมอยู่ในไฟล์ cpp ก่อน

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


หากคุณปล่อยให้การใช้งานออกจากไฟล์ส่วนหัวขอโทษฉัน แต่ดูเหมือนว่าอินเตอร์เฟส Java กับฉันใช่ไหม?
gansub

1

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

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

ฉันยังคงเรียนรู้เหมือนคุณ :)


ขอบคุณสำหรับคำแนะนำหนังสือ ผมไม่ทราบว่าผมจะเคยได้รับไปยังขั้นตอนของการทำขนาดใหญ่ c ++ โปรแกรมแม้ว่า ...
ialm

มันสนุกที่จะเขียนโปรแกรมขนาดใหญ่และสำหรับความท้าทายมากมาย ผมเริ่มชอบมัน :)
pankajt

1

มันเหมือนกับการเขียนหนังสือคุณต้องการพิมพ์บทที่เสร็จแล้วเพียงครั้งเดียว

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

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

กลับไปที่ซอฟต์แวร์: ฉันมี Linux และ Ruby src วางอยู่รอบ ๆ การวัดคร่าวๆของบรรทัดของรหัส ...

     Linux       Ruby
   100,000    100,000   core functionality (just kernel/*, ruby top level dir)
10,000,000    200,000   everything 

หนึ่งในสี่หมวดหมู่นั้นมีโค้ดจำนวนมากดังนั้นจึงจำเป็นต้องมีโมดูล รหัสฐานแบบนี้เป็นเรื่องปกติของระบบโลกแห่งความเป็นจริง

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