Có phải là thực hành tốt để dựa vào các tiêu đề được đưa vào quá cảnh?


37

Tôi đang dọn dẹp bao gồm trong một dự án C ++ mà tôi đang làm việc và tôi luôn tự hỏi liệu tôi có nên bao gồm rõ ràng tất cả các tiêu đề được sử dụng trực tiếp trong một tệp cụ thể hay không hoặc chỉ nên bao gồm mức tối thiểu.

Đây là một ví dụ , Entity.hpp:

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

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

(Hãy giả sử rằng một tuyên bố chuyển tiếp cho RenderObjectkhông phải là một tùy chọn.)

Bây giờ, tôi biết điều đó RenderObject.hppbao gồm Texture.hpp- tôi biết điều đó bởi vì mỗi người RenderObjectcó một Texturethành viên. Tuy nhiên, tôi rõ ràng bao gồm Texture.hpptrong Entity.hpp, bởi vì tôi không chắc liệu có nên dựa vào nó hay không RenderObject.hpp.

Vậy: Có thực hành tốt hay không?


19
Đâu là những người bảo vệ trong ví dụ của bạn? Bạn chỉ vô tình quên chúng, tôi hy vọng?
Doc Brown

3
Một vấn đề xảy ra khi bạn không bao gồm tất cả các tệp được sử dụng là đôi khi thứ tự bạn bao gồm các tệp sau đó trở nên quan trọng. Điều đó thực sự khó chịu chỉ trong trường hợp duy nhất xảy ra nhưng đôi khi quả cầu tuyết và bạn cuối cùng thực sự mong muốn người viết mã như thế sẽ được diễu hành trước một đội bắn.
Dunk

Đây là lý do tại sao có #ifndef _RENDER_H #define _RENDER_H ... #endif.
sampathsris

@Dunk Tôi nghĩ bạn đã hiểu nhầm vấn đề. Với một trong những gợi ý của anh ấy không nên xảy ra.
Vịt Mooing

1
@DocBrown, #pragma oncegiải quyết nó, không?
Pacerier

Câu trả lời:


65

Bạn phải luôn luôn bao gồm tất cả các tiêu đề xác định bất kỳ đối tượng nào được sử dụng trong tệp .cpp trong tệp đó bất kể bạn biết gì về những gì trong các tệp đó. Bạn nên bao gồm các vệ sĩ trong tất cả các tệp tiêu đề để đảm bảo rằng bao gồm các tiêu đề nhiều lần không thành vấn đề.

Những lý do:

  • Điều này cho thấy rõ ràng đối với các nhà phát triển đọc nguồn chính xác những gì tệp nguồn trong câu hỏi yêu cầu. Ở đây, ai đó nhìn vào một vài dòng đầu tiên trong tệp có thể thấy rằng bạn đang xử lý Texturecác đối tượng trong tệp này.
  • Điều này tránh các vấn đề trong đó các tiêu đề được cấu trúc lại gây ra các vấn đề biên dịch khi chúng không còn yêu cầu các tiêu đề cụ thể nữa. Ví dụ, giả sử bạn nhận ra rằng RenderObject.hppthực sự không cần Texture.hppchính nó.

Một hệ quả là bạn không bao giờ nên bao gồm một tiêu đề trong một tiêu đề khác trừ khi nó rõ ràng là cần thiết trong tệp đó.


10
Đồng ý với hệ quả tất yếu - với proviso, LUÔN LUÔN bao gồm các tiêu đề khác nếu nó KHÔNG cần nó!
Andrew

1
Tôi không thích thực hành trực tiếp bao gồm các tiêu đề cho tất cả các lớp cá nhân. Tôi ủng hộ các tiêu đề tích lũy. Đó là, tôi nghĩ rằng tệp cấp cao nên tham chiếu một "mô-đun" nào đó mà nó đang sử dụng, nhưng không cần trực tiếp bao gồm tất cả các phần riêng lẻ.
edA-qa mort-ora-y

8
Điều đó dẫn đến các tiêu đề lớn, nguyên khối mà mọi tệp bao gồm, ngay cả khi chúng chỉ cần một chút của những gì có trong đó, dẫn đến thời gian biên dịch dài và khiến việc tái cấu trúc trở nên khó khăn hơn.
Gort Robot

6
Google đã xây dựng một công cụ để giúp nó thực thi chính xác lời khuyên này, được gọi là bao gồm những gì bạn đang sử dụng .
Matthew G.

3
Vấn đề thời gian biên dịch chính với các tiêu đề nguyên khối lớn không phải là thời gian để tự biên dịch mã tiêu đề mà phải biên dịch mọi tệp cpp trong ứng dụng của bạn mỗi khi tiêu đề thay đổi. Tiêu đề được biên dịch trước không giúp điều đó.
Gort Robot

23

Nguyên tắc chung là: bao gồm những gì bạn sử dụng. Nếu bạn sử dụng một đối tượng trực tiếp, sau đó bao gồm tệp tiêu đề của nó trực tiếp. Nếu bạn sử dụng một đối tượng A sử dụng B nhưng không sử dụng B cho mình, chỉ bao gồm Ah

Ngoài ra, trong khi chúng tôi đang ở trong chủ đề, bạn chỉ nên bao gồm các tệp tiêu đề khác trong tệp tiêu đề của mình nếu bạn thực sự cần nó trong tiêu đề. Nếu bạn chỉ cần nó trong .cpp, thì chỉ bao gồm nó ở đó: đây là sự khác biệt giữa phụ thuộc công khai và riêng tư và sẽ ngăn người dùng của lớp bạn kéo theo các tiêu đề mà họ không thực sự cần.


10

Tôi tiếp tục tự hỏi liệu tôi có nên bao gồm rõ ràng tất cả các tiêu đề được sử dụng trực tiếp trong một tệp cụ thể không

Vâng.

Bạn không bao giờ biết khi nào những tiêu đề khác có thể thay đổi. Nó làm cho tất cả các ý nghĩa trên thế giới bao gồm, trong mỗi đơn vị dịch thuật, các tiêu đề bạn biết rằng đơn vị dịch thuật cần.

Chúng tôi có các nhân viên bảo vệ tiêu đề để đảm bảo rằng việc đưa vào hai lần là không có hại.


3

Ý kiến ​​khác nhau về vấn đề này, nhưng tôi cho rằng mọi tệp (dù là tệp nguồn c / cpp hoặc tệp tiêu đề h / hpp) đều có thể được biên dịch hoặc phân tích riêng.

Như vậy, tất cả các tệp nên #inc loại trừ bất kỳ và tất cả các tệp tiêu đề mà chúng cần - bạn không nên cho rằng một tệp tiêu đề đã được bao gồm trước đó.

Thật là một nỗi đau thực sự nếu bạn cần thêm một tệp tiêu đề và thấy rằng nó sử dụng một mục được xác định ở nơi khác, mà không bao gồm trực tiếp nó ... vì vậy bạn phải đi tìm (và có thể kết thúc với sai!)

Mặt khác, nó không (như một quy tắc chung) không quan trọng nếu bạn # bao gồm một tệp bạn không cần ...


Theo quan điểm của phong cách cá nhân, tôi sắp xếp các tệp #incoide theo thứ tự bảng chữ cái, chia thành hệ thống và ứng dụng - điều này giúp củng cố thông điệp "khép kín và hoàn toàn mạch lạc".


Lưu ý về thứ tự bao gồm: đôi khi thứ tự rất quan trọng, ví dụ như khi bao gồm các tiêu đề X11. Điều này có thể là do thiết kế (có thể trong trường hợp đó được coi là thiết kế xấu), đôi khi đó là do các vấn đề không tương thích đáng tiếc.
hyde

Một lưu ý về việc bao gồm các tiêu đề không cần thiết, không quan trọng đến thời gian biên dịch, trước tiên là trực tiếp (đặc biệt nếu đó là C ++ nặng mẫu), nhưng đặc biệt là khi bao gồm các tiêu đề của cùng dự án hoặc phụ thuộc nơi tệp bao gồm cũng thay đổi và sẽ kích hoạt biên dịch lại tất cả mọi thứ bao gồm nó (nếu bạn có phụ thuộc làm việc, nếu bạn không thì bạn phải thực hiện xây dựng sạch mọi lúc ...).
hyde

2

Nó phụ thuộc vào việc bao gồm quá độ là do sự cần thiết (ví dụ: lớp cơ sở) hoặc do một chi tiết thực hiện (thành viên riêng).

Để làm rõ, việc bao gồm quá độ là cần thiết khi loại bỏ nó chỉ có thể được thực hiện sau khi thay đổi giao diện đầu tiên được khai báo trong tiêu đề trung gian. Vì đó đã là một thay đổi đột phá, bất kỳ tệp .cpp nào sử dụng nó đều phải được kiểm tra.

Ví dụ: Ah được bao gồm bởi Bh được C.cpp sử dụng. Nếu Bh sử dụng Ah cho một số chi tiết triển khai, thì C.cpp không nên cho rằng Bh sẽ tiếp tục làm như vậy. Nhưng nếu Bh sử dụng Ah cho một lớp cơ sở, thì C.cpp có thể cho rằng Bh sẽ tiếp tục bao gồm các tiêu đề có liên quan cho các lớp cơ sở của nó.

Bạn thấy ở đây lợi thế thực tế của việc không trùng lặp các vùi tiêu đề. Nói rằng lớp cơ sở được Bh sử dụng thực sự không thuộc về Ah và được tái cấu trúc thành chính Bh. Bh bây giờ là một tiêu đề độc lập. Nếu C.cpp dự phòng bao gồm Ah, thì bây giờ nó bao gồm một tiêu đề không cần thiết.


2

Có thể có một trường hợp khác: Bạn có Ah, Bh và C.cpp của bạn, Bh bao gồm Ah

vì vậy trong C.cpp, bạn có thể viết

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

Vì vậy, nếu bạn không viết #inc loại "Ah" ở đây, điều gì có thể xảy ra? trong C.cpp của bạn, cả A và B (ví dụ: lớp) được sử dụng. Sau đó, bạn đã thay đổi mã C.cpp của mình, xóa B những thứ liên quan, nhưng để lại Bh bao gồm ở đó.

Nếu bạn bao gồm cả Ah và Bh và tại thời điểm này, các công cụ phát hiện không cần thiết bao gồm có thể giúp bạn chỉ ra rằng Bh bao gồm không còn cần thiết nữa. Nếu bạn chỉ bao gồm Bh như trên, thì thật khó để các công cụ / con người phát hiện ra sự bao gồm không cần thiết sau khi thay đổi mã của bạn.


1

Tôi đang thực hiện một cách tiếp cận hơi khác nhau từ các câu trả lời được đề xuất.

Trong các tiêu đề, luôn luôn chỉ bao gồm một mức tối thiểu, chỉ những gì cần thiết để thực hiện quá trình biên dịch. Sử dụng khai báo chuyển tiếp bất cứ nơi nào có thể.

Trong các tệp nguồn, điều quan trọng là bạn bao gồm bao nhiêu. Sở thích của tôi vẫn là bao gồm tối thiểu để làm cho nó vượt qua.

Đối với các dự án nhỏ, bao gồm các tiêu đề ở đây và sẽ không tạo ra sự khác biệt. Nhưng đối với các dự án vừa và lớn, nó có thể trở thành một vấn đề. Ngay cả khi phần cứng mới nhất được sử dụng để biên dịch, sự khác biệt có thể nhận thấy. Lý do là trình biên dịch vẫn phải mở tiêu đề đi kèm và phân tích cú pháp. Vì vậy, để tối ưu hóa quá trình xây dựng, hãy áp dụng kỹ thuật trên (bao gồm mức tối thiểu trần và sử dụng khai báo chuyển tiếp).

Mặc dù hơi lỗi thời, Thiết kế phần mềm quy mô lớn C ++ (của John Lakos) giải thích tất cả điều này một cách chi tiết.


1
Không đồng ý với chiến lược này ... nếu bạn bao gồm tệp tiêu đề trong tệp nguồn, thì bạn phải theo dõi tất cả các phụ thuộc của nó. Nó là tốt hơn để bao gồm trực tiếp, hơn là thử và ghi lại danh sách!
Andrew

@Andrew có các công cụ và tập lệnh để kiểm tra cái gì, và bao nhiêu lần, được bao gồm.
BЈовић

1
Tôi đã nhận thấy tối ưu hóa trên một số trình biên dịch mới nhất để đối phó với điều này. Họ nhận ra một tuyên bố bảo vệ điển hình, và xử lý nó. Sau đó, khi # bao gồm nó một lần nữa, họ có thể tối ưu hóa hoàn toàn việc tải tệp. Tuy nhiên, khuyến nghị của bạn về khai báo chuyển tiếp là rất khôn ngoan để giảm số lượng bao gồm. Khi bạn bắt đầu sử dụng khai báo chuyển tiếp, nó sẽ trở thành sự cân bằng về thời gian chạy của trình biên dịch (được cải thiện bằng khai báo chuyển tiếp) và thân thiện với người dùng (được cải thiện bằng thêm #includes tiện lợi), đây là sự cân bằng mà mỗi công ty đặt ra khác nhau.
Cort Ammon

1
@CortAmmon Một tiêu đề điển hình bao gồm các vệ sĩ, nhưng trình biên dịch vẫn phải mở nó và đó là hoạt động chậm
BЈовић

4
@ BЈовић: Thật ra, họ không. Tất cả những gì họ phải làm là nhận ra rằng tệp có các bộ bảo vệ tiêu đề "điển hình" và gắn cờ chúng để nó chỉ mở một lần. Gcc, chẳng hạn, có tài liệu về thời điểm và nơi áp dụng tối ưu hóa này: gcc.gnu.org/onlinesocs/cppi INTERNals / Guard
Cort Ammon

-4

Thực hành tốt là không lo lắng về chiến lược tiêu đề của bạn miễn là nó được biên dịch.

Phần tiêu đề của mã của bạn chỉ là một khối các dòng mà không ai nên nhìn vào cho đến khi bạn gặp một lỗi biên dịch dễ dàng giải quyết. Tôi hiểu mong muốn cho phong cách 'chính xác', nhưng không cách nào thực sự có thể được mô tả là chính xác. Việc bao gồm một tiêu đề cho mọi lớp có nhiều khả năng gây ra các lỗi biên dịch dựa trên thứ tự gây phiền nhiễu, nhưng các lỗi biên dịch đó cũng phản ánh các vấn đề mà mã hóa cẩn thận có thể khắc phục (mặc dù có thể cho rằng chúng không đáng để dành thời gian để sửa).

Và vâng, bạn sẽ có những vấn đề dựa trên đơn hàng khi bạn bắt đầu vào friendđất liền.

Bạn có thể nghĩ về vấn đề trong hai trường hợp.


Trường hợp 1: Bạn có một số lượng nhỏ các lớp tương tác với nhau, nói chưa đến một tá. Bạn thường xuyên thêm vào, xóa khỏi và sửa đổi các tiêu đề này theo những cách có thể ảnh hưởng đến sự phụ thuộc của chúng vào nhau. Đây là trường hợp mà ví dụ mã của bạn gợi ý.

Tập hợp các tiêu đề đủ nhỏ để không giải quyết bất kỳ vấn đề nào phát sinh. Bất kỳ vấn đề khó khăn nào đều được khắc phục bằng cách viết lại một hoặc hai tiêu đề. Lo lắng về chiến lược tiêu đề của bạn là giải quyết các vấn đề không tồn tại.


Trường hợp 2: Bạn có hàng tá lớp học. Một số lớp đại diện cho xương sống của chương trình của bạn và viết lại các tiêu đề của chúng sẽ buộc bạn phải viết lại / biên dịch lại một lượng lớn cơ sở mã của bạn. Các lớp khác sử dụng xương sống này để hoàn thành mọi việc. Điều này đại diện cho một thiết lập kinh doanh điển hình. Các tiêu đề được trải đều trên các thư mục và bạn thực sự không thể nhớ tên của mọi thứ.

Giải pháp: Tại thời điểm này, bạn cần nghĩ về các lớp học của mình trong các nhóm logic và thu thập các nhóm đó thành các tiêu đề khiến bạn không phải #includelặp đi lặp lại. Điều này không chỉ làm cho cuộc sống đơn giản hơn, nó cũng là một bước cần thiết để tận dụng các tiêu đề được biên dịch trước .

Bạn kết thúc #includelớp học bạn không cần nhưng ai quan tâm ?

Trong trường hợp này, mã của bạn sẽ trông như ...

#include <Graphics.hpp>

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

13
Tôi đã phải -1 điều này bởi vì tôi thành thật tin rằng bất kỳ câu nào ở dạng "Thực hành tốt là không phải lo lắng về chiến lược ____ của bạn miễn là nó biên dịch" dẫn mọi người đến phán xét tồi. Tôi đã phát hiện ra rằng phương pháp này dẫn rất nhanh đến việc không thể đọc được và không thể đọc được cũng tệ như "không hoạt động". Tôi cũng đã tìm thấy nhiều thư viện lớn không đồng ý với kết quả từ cả hai trường hợp bạn mô tả. Ví dụ, Boost DOES thực hiện các tiêu đề "bộ sưu tập" mà bạn đề xuất trong trường hợp 2, nhưng chúng cũng tạo ra một phần lớn trong việc cung cấp các tiêu đề theo từng lớp khi bạn cần chúng.
Cort Ammon

3
Cá nhân tôi đã chứng kiến ​​"đừng lo lắng nếu nó biên dịch" biến thành "ứng dụng của chúng tôi mất 30 phút để biên dịch khi bạn thêm một giá trị vào enum, làm thế quái nào chúng ta sửa được điều đó!?"
Gort Robot

Tôi đã giải quyết vấn đề thời gian biên dịch trong câu trả lời của tôi. Trong thực tế, câu trả lời của tôi là một trong hai (không ai trong số đó đạt điểm cao). Nhưng thực sự, đó là tiếp tuyến cho câu hỏi của OP; đây là "Tôi có nên lạc đà trong trường hợp tên biến của mình không?" loại câu hỏi. Tôi nhận ra câu trả lời của mình là không phổ biến nhưng không phải lúc nào cũng có cách thực hành tốt nhất cho mọi thứ và đây là một trong những trường hợp đó.
Câu hỏi

Đồng ý với # 2. Theo các ý tưởng trước đó - tôi hy vọng tự động hóa sẽ cập nhật khối tiêu đề cục bộ - cho đến khi đó tôi ủng hộ một danh sách đầy đủ.
chux - Phục hồi Monica

Cách tiếp cận "bao gồm mọi thứ và bồn rửa nhà bếp" có thể giúp bạn tiết kiệm thời gian ban đầu - các tệp tiêu đề của bạn thậm chí có thể trông nhỏ hơn (vì hầu hết mọi thứ được đưa vào gián tiếp từ .... ở đâu đó). Cho đến khi bạn đến điểm mà bất kỳ thay đổi ở bất cứ đâu gây ra sự lặp lại hơn 30 phút cho dự án của bạn. Và tính năng tự động hoàn thành thông minh IDE của bạn đưa ra hàng trăm đề xuất không liên quan. Và bạn vô tình trộn lẫn hai lớp hoặc các hàm tĩnh được đặt tên quá giống nhau. Và bạn thêm một cấu trúc mới nhưng sau đó quá trình xây dựng thất bại vì bạn có xung đột không gian tên với một lớp hoàn toàn không liên quan ở đâu đó ...
CharonX 20/03/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.