เป็นการดีหรือไม่ที่จะต้องพึ่งพาส่วนหัวที่รวมอยู่ในทรานซิสชัน?


37

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

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

#include "RenderObject.hpp"
#include "Texture.hpp"

struct Entity {
    Texture texture;
    RenderObject render();
}

(สมมติว่าการประกาศล่วงหน้าสำหรับRenderObjectไม่ใช่ตัวเลือก)

ตอนนี้ฉันรู้ว่าRenderObject.hppรวมถึงTexture.hpp- ฉันรู้ว่าเพราะแต่ละคนRenderObjectมีTextureสมาชิก ยังคงฉันอย่างชัดเจนรวมถึงTexture.hppในเพราะฉันไม่แน่ใจว่ามันเป็นความคิดที่ดีที่จะพึ่งพามันถูกรวมอยู่ในEntity.hppRenderObject.hpp

ดังนั้น: เป็นการปฏิบัติที่ดีหรือไม่?


19
ยามรวมอยู่ในตัวอย่างของคุณอยู่ที่ไหน คุณลืมพวกเขาไปโดยบังเอิญฉันหวังว่า?
Doc Brown

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

#ifndef _RENDER_H #define _RENDER_H ... #endifนี่คือเหตุผลที่มี
sampathsris

@Dunk ฉันคิดว่าคุณเข้าใจผิดปัญหา ด้วยคำแนะนำของเขาอย่างใดอย่างหนึ่งที่ไม่ควรเกิดขึ้น
Mooing Duck

1
@DocBrown #pragma onceตัดสินมันไม่?
Pacerier

คำตอบ:


65

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

เหตุผล:

  • สิ่งนี้ทำให้ชัดเจนสำหรับนักพัฒนาที่อ่านแหล่งที่มาตรงกับสิ่งที่ไฟล์ที่มาถาม ที่นี่มีคนดูที่สองสามบรรทัดแรกในไฟล์จะเห็นว่าคุณกำลังจัดการกับTextureวัตถุในไฟล์นี้
  • สิ่งนี้จะช่วยหลีกเลี่ยงปัญหาที่ส่วนหัวที่ได้รับการจัดโครงสร้างใหม่ทำให้เกิดปัญหาการรวบรวมเมื่อไม่จำเป็นต้องใช้ส่วนหัวอีกต่อไป ตัวอย่างเช่นสมมติว่าคุณรู้ว่าRenderObject.hppไม่ต้องการTexture.hppตัวเอง

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


10
เห็นด้วยกับข้อพิสูจน์ - ด้วยเงื่อนไขที่ควรรวมส่วนหัวอื่น ๆ ถ้ามันต้องการ!
Andrew

1
ฉันไม่ชอบการฝึกฝนโดยตรงรวมถึงส่วนหัวสำหรับชั้นเรียนส่วนบุคคลทั้งหมด ฉันชอบส่วนหัวสะสม นั่นคือฉันคิดว่าไฟล์ระดับสูงควรอ้างอิง "โมดูล" บางชนิดที่ใช้ แต่ไม่จำเป็นต้องรวมทุกส่วนโดยตรง
edA-qa mort-ora-y

8
ซึ่งนำไปสู่ส่วนหัวเสาหินขนาดใหญ่ที่ทุกไฟล์รวมถึงแม้ว่าพวกเขาต้องการเพียงเล็กน้อยของสิ่งที่อยู่ในนั้นซึ่งนำไปสู่การรวบรวมเวลานานและทำให้การปรับโครงสร้างยากขึ้น
Gort the Robot

6
Google ได้สร้างเครื่องมือเพื่อช่วยให้การบังคับใช้มันได้อย่างแม่นยำคำแนะนำนี้เรียกว่ารวมถึงสิ่งที่คุณใช้งาน
Matthew G.

3
ปัญหาเวลารวบรวมหลักที่มีส่วนหัวเสาหินขนาดใหญ่ไม่ใช่เวลารวบรวมรหัสส่วนหัวเอง แต่ต้องคอมไพเลอร์ทุกไฟล์ cpp ในแอปพลิเคชันของคุณทุกครั้งที่มีการเปลี่ยนแปลงส่วนหัว ส่วนหัวที่รวบรวมไว้ล่วงหน้าไม่ได้ช่วยอะไร
Gort the Robot

23

กฎทั่วไปของหัวแม่มือคือ: รวมสิ่งที่คุณใช้ หากคุณใช้วัตถุโดยตรงให้รวมไฟล์ส่วนหัวโดยตรง หากคุณใช้วัตถุ A ที่ใช้ B แต่ห้ามใช้ B ด้วยตัวคุณเองให้ใส่ Ah เท่านั้น

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


10

ฉันสงสัยอยู่เสมอว่าควรรวมส่วนหัวทั้งหมดที่ใช้โดยตรงในไฟล์เฉพาะหรือไม่

ใช่.

คุณไม่มีทางรู้ว่าส่วนหัวอื่น ๆ เหล่านั้นอาจมีการเปลี่ยนแปลง มันทำให้รู้สึกทุกอย่างในโลกที่จะรวมไว้ในหน่วยการแปลแต่ละหน่วยส่วนหัวที่คุณรู้ว่าหน่วยการแปลนั้นต้องการ

เรามีการ์ดส่วนหัวเพื่อให้แน่ใจว่าการรวมสองครั้งไม่เป็นอันตราย


3

ความคิดเห็นที่แตกต่างกันในเรื่องนี้ แต่ฉันเห็นว่าทุกไฟล์ (ไม่ว่าจะเป็นไฟล์ต้นฉบับ c / cpp หรือไฟล์ส่วนหัว h / hpp) ควรจะสามารถรวบรวมหรือวิเคราะห์ด้วยตัวเอง

ดังนั้นไฟล์ทั้งหมดควรรวม # ไฟล์ใด ๆ และส่วนหัวทั้งหมดที่พวกเขาต้องการ - คุณไม่ควรคิดว่าไฟล์ส่วนหัวหนึ่งไฟล์ได้รวมไว้ก่อนหน้านี้แล้ว

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

ในทางกลับกันมันไม่สำคัญ (ตามกฎทั่วไป) หากคุณรวมไฟล์ที่คุณไม่ต้องการ ...


ในฐานะที่เป็นสไตล์ส่วนตัวฉันจัดเรียงไฟล์ #include ตามลำดับตัวอักษรแบ่งออกเป็นระบบและแอปพลิเคชัน - ซึ่งจะช่วยเสริมข้อความ "มีอยู่ในตัวและสอดคล้องกันอย่างเต็มที่"


หมายเหตุเกี่ยวกับลำดับการรวม: บางครั้งคำสั่งซื้อมีความสำคัญเช่นเมื่อรวมส่วนหัว X11 นี่อาจเกิดจากการออกแบบ (ซึ่งในกรณีนี้อาจถือว่าเป็นการออกแบบที่ไม่ดี) บางครั้งมันเกิดจากปัญหาความไม่ลงรอยกันที่โชคร้าย
hyde

หมายเหตุเกี่ยวกับการรวมส่วนหัวที่ไม่จำเป็นมันมีความสำคัญในการรวบรวมครั้งแรกโดยตรง (โดยเฉพาะอย่างยิ่งถ้ามันเป็นแม่แบบหนัก C ++) แต่โดยเฉพาะอย่างยิ่งเมื่อรวมส่วนหัวของโครงการเดียวกันหรือพึ่งพาที่ไฟล์รวมถึงการเปลี่ยนแปลงและจะทริกเกอร์ ทุกอย่างรวมถึงมัน (ถ้าคุณมีการพึ่งพาการทำงานถ้าคุณไม่ทำแล้วคุณจะต้องทำความสะอาดสร้างตลอดเวลา ... )
hyde

2

ขึ้นอยู่กับว่าการรวมสกรรมกริยานั้นเป็นไปตามความจำเป็น (เช่นคลาสฐาน) หรือเนื่องจากรายละเอียดการปฏิบัติ (สมาชิกส่วนตัว)

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

ตัวอย่าง: Ah ถูกรวมโดย Bh ซึ่งใช้โดย C.cpp ถ้า Bh ใช้ Ah เพื่อดูรายละเอียดการนำไปใช้งานบางอย่าง C.cpp ไม่ควรถือว่า Bh จะดำเนินการต่อไป แต่ถ้า Bh ใช้ Ah สำหรับคลาสพื้นฐาน C.cpp อาจสันนิษฐานว่า Bh จะยังคงรวมส่วนหัวที่เกี่ยวข้องสำหรับคลาสพื้นฐานไว้

คุณจะเห็นประโยชน์ที่แท้จริงของการรวมส่วนหัวไม่ซ้ำกัน บอกว่าชั้นฐานที่ใช้โดย Bh ไม่ได้เป็นของ Ah จริงๆและได้รับการดัดแปลงให้เป็น Bh Bh เป็นส่วนหัวแบบสแตนด์อโลน หาก C.cpp รวม Ah ซ้ำซ้อนตอนนี้จะรวมส่วนหัวที่ไม่จำเป็น


2

อาจมีอีกกรณีหนึ่ง: คุณมี Ah, Bh และ C.cpp, Bh ของคุณรวมถึง Ah

ดังนั้นใน C.cpp คุณสามารถเขียนได้

#include "B.h"
#include "A.h" // < this can be optional as B.h already has all the stuff in A.h

ดังนั้นถ้าคุณไม่เขียน #include "Ah" ที่นี่จะเกิดอะไรขึ้นได้บ้าง ใน C.cpp ของคุณจะใช้ทั้ง A และ B (เช่นคลาส) หลังจากนั้นคุณเปลี่ยนรหัส C.cpp ของคุณลบสิ่งที่เกี่ยวข้องกับ B แต่ทิ้ง Bh ไว้ที่นั่น

หากคุณรวมทั้ง Ah และ Bh และ ณ จุดนี้เครื่องมือที่ตรวจจับการรวมที่ไม่จำเป็นอาจช่วยให้คุณชี้ให้เห็นว่า Bh include ไม่จำเป็นอีกต่อไป หากคุณรวม Bh ไว้ข้างต้นเท่านั้นมันเป็นเรื่องยากสำหรับเครื่องมือ / มนุษย์ในการตรวจสอบการรวมที่ไม่จำเป็นหลังจากเปลี่ยนรหัสของคุณ


1

ฉันใช้แนวทางที่แตกต่างกันเล็กน้อยจากคำตอบที่เสนอ

ในส่วนหัวให้รวมเพียงขั้นต่ำเปล่าเสมอสิ่งที่จำเป็นในการสร้างรหัสผ่าน ใช้การประกาศไปข้างหน้าทุกที่ที่ทำได้

ในไฟล์ต้นฉบับมันไม่สำคัญเท่าไรที่คุณจะมี การตั้งค่าของฉันยังคงรวมถึงขั้นต่ำเพื่อให้ผ่าน

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

แม้ว่าจะล้าสมัยไปเล็กน้อยการออกแบบซอฟต์แวร์ C ++ ขนาดใหญ่ (โดย John Lakos) อธิบายรายละเอียดทั้งหมดนี้ได้


1
ไม่เห็นด้วยกับกลยุทธ์นี้ ... หากคุณรวมไฟล์ส่วนหัวในไฟล์ต้นฉบับคุณต้องติดตามการอ้างอิงทั้งหมด มันจะดีกว่าที่จะรวมโดยตรงกว่าลองและบันทึกรายการ!
Andrew

@Andrew มีเครื่องมือและสคริปต์เพื่อตรวจสอบสิ่งที่และกี่ครั้งรวมอยู่
BЈовић

1
ฉันสังเกตเห็นการปรับให้เหมาะสมกับคอมไพเลอร์ล่าสุดบางตัวเพื่อจัดการกับสิ่งนี้ พวกเขารู้จักคำแถลงความปลอดภัยทั่วไปและดำเนินการ จากนั้นเมื่อ #including อีกครั้งพวกเขาสามารถเพิ่มประสิทธิภาพการโหลดไฟล์ได้อย่างสมบูรณ์ อย่างไรก็ตามคำแนะนำของคุณเกี่ยวกับการประกาศล่วงหน้านั้นเป็นเรื่องที่ฉลาดมากที่จะลดจำนวนข้อเสนอ เมื่อคุณเริ่มใช้การประกาศไปข้างหน้ามันจะกลายเป็นความสมดุลของคอมไพเลอร์รันไทม์ (ปรับปรุงโดยการประกาศไปข้างหน้า) และความเป็นมิตรต่อผู้ใช้
Cort Ammon

1
@CortAmmon ส่วนหัวทั่วไปมีเจ้าหน้าที่รักษาความปลอดภัย แต่คอมไพเลอร์ยังคงต้องเปิดและนั่นคือการทำงานช้า
BЈовић

4
@ BЈовић: จริง ๆ แล้วพวกเขาทำไม่ได้ สิ่งที่พวกเขาต้องทำคือจำได้ว่าไฟล์นั้นมีการ์ดส่วนหัว "ทั่วไป" และติดธงไว้เพื่อเปิดเพียงครั้งเดียวเท่านั้น ตัวอย่างเช่นGcc
Cort Ammon

-4

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

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

และใช่คุณจะมีปัญหาตามคำสั่งเหล่านั้นเมื่อคุณเริ่มเข้าสู่friendดินแดน

คุณสามารถคิดถึงปัญหาในสองกรณี


กรณีที่ 1: คุณมีคลาสเล็ก ๆ จำนวนหนึ่งโต้ตอบกันพูดน้อยกว่าหนึ่งโหล คุณเพิ่มลบและปรับเปลี่ยนส่วนหัวเหล่านี้เป็นประจำในลักษณะที่อาจส่งผลกระทบต่อการพึ่งพากัน นี่เป็นกรณีที่ตัวอย่างรหัสของคุณแนะนำ

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


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

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

คุณจบการ#includeเรียนที่คุณไม่ต้องการ แต่ใครจะสนใจ ?

ในกรณีนี้รหัสของคุณจะเป็น ...

#include <Graphics.hpp>

struct Entity {
    Texture texture;
    RenderObject render();
}

13
ฉันต้อง -1 สิ่งนี้เพราะฉันเชื่อโดยสุจริตว่าประโยคใด ๆ ที่อยู่ในรูปแบบของ "การปฏิบัติที่ดีคือไม่ต้องกังวลกับกลยุทธ์ ____ ของคุณตราบใดที่มันรวบรวม" นำไปสู่การตัดสินที่ไม่ดี ฉันพบว่าวิธีการที่นำไปสู่การอ่านอย่างรวดเร็วมากและการอ่านไม่ได้นั้นเกือบจะแย่เหมือน "ไม่ทำงาน" ฉันได้พบห้องสมุดสำคัญหลายแห่งที่ไม่เห็นด้วยกับผลลัพธ์จากทั้งสองกรณีที่คุณอธิบาย ตัวอย่างเช่น Boost DOES ทำส่วนหัว "คอลเลกชัน" ที่คุณแนะนำในกรณีที่ 2 แต่พวกเขาก็ทำเรื่องใหญ่จากการให้ส่วนหัวแบบคลาสต่อชั้นเมื่อคุณต้องการ
Cort Ammon

3
ฉันได้เห็นเป็นการส่วนตัว "ไม่ต้องกังวลถ้ามันรวบรวม" กลายเป็น "แอปพลิเคชันของเราใช้เวลา 30 นาทีในการรวบรวมเมื่อคุณเพิ่มคุณค่าให้กับ enum เราจะแก้ไขปัญหานี้ได้อย่างไร!"
Gort the Robot

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

เห็นด้วยกับ # 2 ตามแนวคิดก่อนหน้านี้ - ฉันหวังว่าระบบอัตโนมัติจะอัปเดตบล็อกส่วนหัวของเครื่อง - จนกว่าฉันจะสนับสนุนรายการที่สมบูรณ์
chux - Reinstate Monica

วิธีการ "รวมทุกอย่างและอ่างล้างจานในครัว" อาจช่วยให้คุณประหยัดเวลาได้ตั้งแต่แรก - ไฟล์ส่วนหัวของคุณอาจดูเล็กลง (เนื่องจากส่วนใหญ่รวมอยู่ทางอ้อมจาก .... ที่อื่น) จนกว่าคุณจะมาถึงจุดที่การเปลี่ยนแปลงใด ๆ ทำให้เกิดการซ้ำอีก 30 นาทีในโครงการของคุณ และการเติมข้อความอัตโนมัติ IDE-smart ของคุณจะแสดงคำแนะนำที่ไม่เกี่ยวข้องหลายร้อยรายการ และคุณตั้งใจผสมสองคลาสที่มีชื่อคล้ายกันมากเกินไปหรือฟังก์ชันคงที่ และคุณเพิ่ม struct ใหม่ แต่แล้วสร้างล้มเหลวเพราะคุณมีการปะทะกัน namespace กับที่ไหนสักแห่งระดับที่ไม่เกี่ยวข้องกันโดยสิ้นเชิง ...
CharonX
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.