2009-11-09 31 views
5

C++ headerC++ Headers - Thực hành tốt nhất khi bao gồm

Nếu tôi có A.cpp và Ah cũng như bh, ch, dh

Tôi có nên làm:

trong Ah:

#include "b.h" 
#include "c.h" 
#include "d.h" 

trong A.cpp:

#include "A.h" 

hoặc

trong A.cpp:

#include "A.h" 
#include "b.h" 
#include "c.h" 
#include "d.h" 

Có vấn đề hiệu suất? Lợi ích rõ ràng? Có điều gì xấu về điều này?

Trả lời

0

thích không bao gồm tiêu đề trong tiêu đề khác - nó chậm biên soạn và leas để tham chiếu vòng tròn

+0

@mgd - Nếu tôi có tiêu đề có 10 bao gồm và nguồn có 10 bao gồm khác nhau, tôi có nên chuyển phần bao gồm từ đầu trang sang nguồn để nguồn có 20 bao gồm không? –

+0

Xem câu trả lời hoàn chỉnh hơn của alex. Một ngoại lệ cho điều này là nếu bạn có tiêu đề được biên dịch trước, trong trường hợp đó bao gồm tất cả tiêu đề nền tảng/khung/hệ điều hành sẽ không bao giờ thay đổi thành một tệp và bao gồm ở mọi nơi –

0

Sẽ không có vấn đề hiệu suất, tất cả đều được thực hiện tại thời gian biên dịch, và các tiêu đề của bạn nên được thiết lập để họ có thể' t được bao gồm nhiều lần.

Tôi không biết nếu có một cách tiêu chuẩn để làm điều đó, nhưng tôi muốn bao gồm tất cả các tiêu đề tôi cần trong tệp nguồn và đưa chúng vào tiêu đề nếu nội dung nào đó trong chính tiêu đề cần nó (ví dụ: typedef từ một tiêu đề khác)

+0

@Jeff - ah phải, các mục trong tiêu đề có thể cần tiêu đề đó. nhưng họ sẽ không nhận được nó nếu tất cả các tiêu đề nằm trong tệp nguồn? –

20

Bạn chỉ nên bao gồm những gì cần thiết để biên dịch; thêm không cần thiết bao gồm sẽ làm tổn thương thời gian biên soạn của bạn, đặc biệt là trong các dự án lớn.

Mỗi tệp tiêu đề sẽ có thể tự biên dịch một cách chính xác - nghĩa là, nếu bạn có tệp nguồn chỉ bao gồm tiêu đề đó, tệp sẽ biên dịch mà không có lỗi. Tệp tiêu đề nên bao gồm không nhiều hơn là cần thiết cho điều đó.

Cố gắng sử dụng các khai báo chuyển tiếp càng nhiều càng tốt. Nếu bạn đang sử dụng lớp, nhưng tệp tiêu đề chỉ đề cập đến con trỏ/tham chiếu đến đối tượng của lớp đó, thì không cần phải bao gồm định nghĩa của lớp - chỉ cần sử dụng khai báo chuyển tiếp:

class SomeClass; 
// Can now use pointers/references to SomeClass 
// without needing the full definition 
+0

@Adam - bạn có ví dụ về các tờ khai chuyển tiếp và khi nào thích hợp để sử dụng và khi nào không? –

+1

Lời khuyên tuyệt vời. Tôi cũng khuyên bạn nên sử dụng tệp .c được liên kết với a .h bao gồm .h là tệp đầu tiên bao gồm. Điều này là để đảm bảo rằng tất cả các tệp .h của bạn luôn có thể tự biên dịch (thay vì dựa vào thứ tự bao gồm trong tệp .c của bạn). –

+0

+1 cho phần cuối cùng, trước đây tôi luôn bao gồm tệp tiêu đề –

3

Những gì Adam Rosenfield nói với bạn là tại chỗ. Một ví dụ về khi bạn có thể sử dụng một tờ khai chuyển tiếp là:

#ifndef A_H_ 
#define A_H_ 

    #include "D.h" 
    class B; //forward declaration 
    class C; //forward declaration 

    class A 
    { 
    B *m_pb; //you can forward declare this one bacause it's a pointer and the compilier doesn't need to know the size of object B at this point in the code. include B.h in the cpp file. 
    C &m_rc; //you can also forware declare this one for the same reason, except it's a reference. 

    D m_d; //you cannot forward declare this one because the complier need to calc the size of object D. 

    }; 

#endif 
+0

(Tôi hy vọng rằng xây dựng, tôi đã không kiểm tra nó lol. Cho tôi biết nếu bạn có vấn đề với nó). – cchampion

6

Việc thực hành quan trọng ở đây là có xung quanh mỗi foo.h nộp một người bảo vệ như:

#ifndef _FOO_H 
#define _FOO_H 

...rest of the .h file... 
#endif 

Điều này ngăn cản nhiều tạp chất, với vòng lặp và tất cả những nỗi kinh hoàng như vậy. Một khi bạn đảm bảo mọi tập tin bao gồm được bảo vệ, các chi tiết cụ thể ít quan trọng hơn.

Tôi thích một nguyên tắc hướng dẫn mà Adam thể hiện: đảm bảo rằng, nếu tệp nguồn chỉ bao gồm a.h, nó sẽ không tránh khỏi lỗi do a.h giả định các tệp khác đã được đưa vào trước đó - ví dụ: nếu a.h yêu cầu b.h để được bao gồm trước đó, nó có thể và chỉ nên bao gồm b.h (các vệ sĩ sẽ làm cho rằng một noop nếu b.h đã bao gồm trước)

Ngược lại, một file nguồn nên bao gồm các tiêu đề mà từ đó nó được đòi hỏi một cái gì đó (macro, tờ khai, vv), không cho rằng tiêu đề khác chỉ đến trong một cách kỳ diệu bởi vì nó có bao gồm một số.

Nếu bạn đang sử dụng các lớp học theo giá trị, than ôi, bạn cần tất cả các chi tiết đẫm máu của lớp học trong một số .h bạn bao gồm. Nhưng đối với một số sử dụng thông qua tài liệu tham khảo hoặc con trỏ, chỉ cần một số class sic; là đủ. Ví dụ, tất cả những thứ khác bằng nhau, nếu lớp a có thể lấy đi một con trỏ thành một thể hiện của lớp b (tức là một thành viên class b *my_bp; chứ không phải là thành viên class b *my_b;), việc ghép nối giữa các tệp có thể trở nên yếu hơn (giảm nhiều biên dịch lại)) -- ví dụ b.h có thể có ít hơn class b; trong khi tất cả các chi tiết đẫm máu trong b_impl.h được bao gồm chỉ bởi các tiêu đề mà thực sự cần nó ...

+0

Cho rằng foo.h sẽ vẫn được đọc trong mỗi khi nó được bao gồm, nó không thực sự là một no-op. Nó sẽ là một no-op nếu bạn đã làm: #ifndef _FOO_H #include "foo.h" #endif – Bill

+0

Vâng, nhưng điều đó sẽ gây ô nhiễm mọi sự bao gồm vào tính không đọc được. Đối với tất cả các ý định và mục đích, một phép lặp lại nhiều lần sẽ được tìm thấy trong bộ nhớ đệm của hệ thống tập tin, vì vậy không có hiệu năng nào được nhấn trong "bao gồm" nó nữa - và chắc chắn nó là một không có từ quan điểm ngữ nghĩa, vấn đề. –

3

trả lời: Hãy A.h bao gồm b.h, c.hd.h chỉ nếu nó cần thiết để làm cho việc xây dựng thành công. Quy tắc chung là: chỉ bao gồm nhiều mã trong tệp tiêu đề nếu cần. Bất cứ điều gì không cần thiết ngay lập tức trong A.h phải được bao gồm bởi A.cpp.

Lý do: Mã ít hơn được đưa vào tệp tiêu đề, bạn càng cần phải biên dịch lại mã sử dụng tệp tiêu đề sau khi thực hiện một số thay đổi ở đâu đó. Nếu có nhiều tham chiếu #include giữa các tệp tiêu đề khác nhau, việc thay đổi bất kỳ tệp nào trong số chúng sẽ yêu cầu xây dựng lại tất cả các tệp khác bao gồm tệp tiêu đề đã thay đổi - recursivel. Vì vậy, nếu bạn quyết định chạm vào một số tập tin tiêu đề toplevel, điều này có thể xây dựng lại các phần lớn của mã của bạn.

Bằng cách sử dụng forward declarations bất cứ khi nào có thể trong tệp tiêu đề, bạn giảm khớp nối của tệp nguồn và do đó làm cho việc xây dựng nhanh hơn. Tuyên bố về phía trước như vậy có thể được sử dụng trong nhiều tình huống hơn bạn nghĩ. Như một quy tắc của ngón tay cái, bạn chỉ cần file header t.h (trong đó xác định một loại T) nếu bạn

  1. Khai báo một biến thành viên kiểu T (lưu ý, điều này không không bao gồm tuyên bố một con trỏ-to- T).
  2. Viết một số hàm nội tuyến truy cập các thành viên của đối tượng thuộc loại T.

Bạn làm không cần phải bao gồm việc kê khai của T nếu tập tin tiêu đề của bạn chỉ

  1. Tuyên bố constructors/chức năng mà lấy tài liệu tham khảo hoặc con trỏ đến một đối tượng T.
  2. Khai báo các hàm trả về đối tượng T theo con trỏ, tham chiếu hoặc giá trị.
  3. Khai báo biến thành viên là tham chiếu hoặc con trỏ đến đối tượng T.

Xem xét khai báo lớp này; mà các tập tin bao gồm cho A, BC bạn có thực sự cần phải bao gồm ?:

class MyClass 
{ 
public: 
    MyClass(const A &a); 

    void set(const B &b); 
    void set(const B *b); 
    B getB(); 
    C getC(); 

private: 
    B *m_b; 
    C m_c; 
}; 

Bạn chỉ cần bao gồm tập tin cho các loại C, vì sự biến thành viên m_c. Bạn thường có thể loại bỏ yêu cầu này bằng cách không khai báo các biến thành viên của bạn trực tiếp nhưng sử dụng một opaque pointer để ẩn tất cả các biến thành viên trong một cấu trúc riêng, để chúng không hiển thị trong tệp tiêu đề nữa.

+0

Bạn đang trộn A, B và C với X, Y và Z trong ví dụ của bạn. – Bill

+0

@Bill: Bạn nói đúng, tôi đã điều chỉnh ví dụ để nói A, B, C ngay bây giờ. –

+0

@FrerichRaabe câu trả lời hoàn hảo. 1 cho phần "Lý luận:". – ParokshaX

Các vấn đề liên quan