C / C ++ bao gồm thứ tự tệp tiêu đề


287

Thứ tự nào nên bao gồm các tệp được chỉ định, nghĩa là những lý do để bao gồm một tiêu đề trước một tiêu đề khác là gì?

Ví dụ: các tệp hệ thống, STL và Boost đi trước hoặc sau tệp cục bộ bao gồm các tệp?


2
Và rất nhiều câu trả lời dưới đây là lý do tại sao các nhà phát triển Java quyết định chống lại các tiêu đề riêng biệt. :-) Một số câu trả lời thực sự tốt, tuy nhiên, đặc biệt là lời khuyên để đảm bảo các tệp tiêu đề của riêng bạn có thể đứng độc lập.
Chris K

37
Tôi thích nó như thế nào những câu hỏi có hơn 100 phiếu bầu và rất thú vị đối với một số người bị đóng cửa là "không mang tính xây dựng".
Andreas

Một rất khuyến khích đọc: cplusplus.com/forum/articles/10627
Kalsan

3
@mrt, SO nhắc nhở rất nhiều về cộng đồng súp Đức Quốc xã: Hoặc là bạn tuân theo một số quy tắc rất nghiêm ngặt hoặc "Không có câu trả lời / Nhận xét thích hợp nào cho bạn!". Tuy nhiên, nếu ai đó có vấn đề liên quan đến lập trình, thì đây thường là trang web đầu tiên ..
Imago

Câu trả lời:


289

Tôi không nghĩ rằng có một thứ tự được đề nghị, miễn là nó được biên dịch! Điều khó chịu là khi một số tiêu đề yêu cầu các tiêu đề khác được đưa vào trước ... Đó là vấn đề với chính các tiêu đề, không phải theo thứ tự bao gồm.

Sở thích cá nhân của tôi là đi từ địa phương đến toàn cầu, mỗi tiểu mục theo thứ tự bảng chữ cái, nghĩa là:

  1. h tập tin tương ứng với tập tin cpp này (nếu có)
  2. tiêu đề từ cùng một thành phần,
  3. tiêu đề từ các thành phần khác,
  4. tiêu đề hệ thống.

Lý do của tôi cho 1. là nó phải chứng minh rằng mỗi tiêu đề (trong đó có một cpp) có thể là #included mà không cần điều kiện tiên quyết (terminus technicus: tiêu đề là "khép kín"). Và phần còn lại dường như chảy một cách hợp lý từ đó.


16
Cũng giống như bạn, ngoại trừ tôi đi từ toàn cầu xuống cục bộ và tiêu đề tương ứng với tệp nguồn không nhận được sự đối xử đặc biệt.
Jon Purdy

127
@Jon: Tôi muốn nói điều đó hoàn toàn ngược lại! :. vì vậy, nếu sau này bạn hoặc người khác, bao gồm mygroup.h nhưng không cần chuỗi, bạn sẽ gặp lỗi cần phải sửa trong cpp hoặc trong chính tiêu đề. Nhưng tôi sẽ quan tâm nếu biết đó là những gì mọi người nghĩ sẽ hoạt động tốt hơn trong thời gian dài ... Tại sao bạn không đăng câu trả lời với đề xuất của mình và chúng tôi sẽ xem ai "chiến thắng"? ;-)
squelart

3
Cụ thể để đặt hàng chung là những gì tôi sử dụng tại thời điểm này từ khuyến nghị của Dave Abrahams. Và ông lưu ý lý do tương tự như @squelart về chiếu sáng tiêu đề bị thiếu bao gồm trong các nguồn, từ địa phương đến tổng quát hơn. Chìa khóa quan trọng là bạn có nhiều khả năng mắc những lỗi đó hơn so với bên thứ 3 và các thư viện hệ thống.
GrafikRobot

7
@PaulJansen Đó là một thực tiễn tồi và thật tốt khi sử dụng một kỹ thuật có khả năng thổi bùng lên để thực hành xấu có thể được sửa chữa thay vì đặt ẩn. FTW địa phương toàn cầu
bames53

10
@PaulJansen Vâng, tôi đã đề cập đến việc ghi đè hành vi tiêu chuẩn. Nó có thể xảy ra do tai nạn, ví dụ như, phá vỡ ODR có thể xảy ra do tai nạn. Giải pháp không phải là sử dụng các thực hành che giấu khi tai nạn như vậy xảy ra, mà là sử dụng các thực tiễn có khả năng khiến chúng nổ tung hết mức có thể, để các lỗi có thể được chú ý và khắc phục sớm nhất có thể.
bames53

106

Điều quan trọng cần ghi nhớ là các tiêu đề của bạn không nên phụ thuộc vào các tiêu đề khác được đưa vào trước. Một cách để đảm bảo điều này là bao gồm các tiêu đề của bạn trước bất kỳ tiêu đề nào khác.

"Suy nghĩ trong C ++" đặc biệt đề cập đến điều này, tham khảo "Thiết kế phần mềm C ++ quy mô lớn" của Lakos:

Có thể tránh được các lỗi sử dụng tiềm ẩn bằng cách đảm bảo rằng tệp .h của một thành phần phân tích cú pháp - không có các khai báo hoặc định nghĩa được cung cấp bên ngoài ... Bao gồm tệp .h là dòng đầu tiên của tệp .c đảm bảo rằng không có phần quan trọng nào thông tin nội tại giao diện vật lý của thành phần bị thiếu trong tệp .h (hoặc, nếu có, bạn sẽ tìm hiểu về nó ngay khi bạn cố gắng biên dịch tệp .c).

Điều đó có nghĩa là, bao gồm theo thứ tự sau:

  1. Tiêu đề nguyên mẫu / giao diện cho việc triển khai này (ví dụ: tệp .h / .hh tương ứng với tệp .cpp / .cc này).
  2. Các tiêu đề khác từ cùng một dự án, khi cần thiết.
  3. Các tiêu đề từ các thư viện phi tiêu chuẩn, phi hệ thống khác (ví dụ: Qt, Eigen, v.v.).
  4. Tiêu đề từ các thư viện "gần như tiêu chuẩn" khác (ví dụ: Boost)
  5. Tiêu đề C ++ tiêu chuẩn (ví dụ: iostream, chức năng, v.v.)
  6. Tiêu đề C tiêu chuẩn (ví dụ: cstdint, dirent.h, v.v.)

Nếu bất kỳ tiêu đề nào có vấn đề với việc được đưa vào thứ tự này, hãy sửa chúng (nếu là của bạn) hoặc không sử dụng chúng. Thư viện tẩy chay không viết tiêu đề sạch.

Hướng dẫn về phong cách C ++ của Google lập luận gần như ngược lại, thực sự không có lý do nào cả; Cá nhân tôi có xu hướng ủng hộ phương pháp Lakos.


13
Đến bây giờ, Google C ++ Style Guide khuyên bạn nên bao gồm tệp tiêu đề có liên quan trước tiên, theo đề xuất của Lakos.
Filip Bártek

Bạn không nhận được nhiều hơn tiêu đề liên quan đầu tiên bởi vì một khi bạn bắt đầu bao gồm các tiêu đề trong dự án của bạn, bạn sẽ kéo theo rất nhiều phụ thuộc hệ thống.
Micah

@Micah - tiêu đề trong dự án kéo theo "rất nhiều phụ thuộc hệ thống" là thiết kế tồi, chính xác là những gì chúng tôi đang cố gắng tránh ở đây. Vấn đề là tránh cả phụ thuộc không cần thiết và phụ thuộc chưa được giải quyết. Tất cả các tiêu đề sẽ có thể được bao gồm mà không bao gồm các tiêu đề khác trước. Trong trường hợp tiêu đề trong dự án cần phụ thuộc hệ thống, thì cũng vậy - bạn không (và không nên) bao gồm phụ thuộc hệ thống sau nó, trừ khi mã cục bộ của tệp đó sử dụng những thứ từ hệ thống đó. Bạn không thể và không nên dựa vào các tiêu đề (thậm chí là của bạn) để bao gồm các hệ thống mà bạn sử dụng.
Nathan Paul Simons

49

Tôi tuân theo hai quy tắc đơn giản để tránh phần lớn các vấn đề:

  1. Tất cả các tiêu đề (và thực sự là bất kỳ tệp nguồn nào ) nên bao gồm những gì họ cần. Họ không nên dựa vào người dùng của họ bao gồm cả những thứ.
  2. Như một sự bổ sung, tất cả các tiêu đề nên bao gồm các vệ sĩ để chúng không được bao gồm nhiều lần bằng cách áp dụng quy tắc 1 đầy tham vọng của quy tắc 1 ở trên.

Tôi cũng làm theo hướng dẫn của:

  1. Bao gồm các tiêu đề hệ thống trước (stdio.h, v.v.) với một đường phân chia.
  2. Nhóm chúng một cách hợp lý.

Nói cách khác:

#include <stdio.h>
#include <string.h>

#include "btree.h"
#include "collect_hash.h"
#include "collect_arraylist.h"
#include "globals.h"

Mặc dù, là hướng dẫn, đó là một điều chủ quan. Mặt khác, các quy tắc, tôi thực thi một cách cứng nhắc, thậm chí đến mức cung cấp các tệp tiêu đề 'bao bọc' bao gồm các vệ sĩ và được nhóm lại bao gồm nếu một số nhà phát triển bên thứ ba đáng ghét không đăng ký vào tầm nhìn của tôi :-)


6
+1 "Tất cả các tiêu đề (và thực sự là bất kỳ tệp nguồn nào) nên bao gồm những gì họ cần. Họ không nên dựa vào người dùng của họ bao gồm mọi thứ." Tuy nhiên, rất nhiều người dựa vào hành vi đưa vào ngầm này với NULL và không bao gồm <cstddef>. Thật khó chịu khi cố gắng chuyển mã này và nhận lỗi biên dịch trên NULL (một lý do tôi chỉ sử dụng 0 bây giờ).
stinky472

20
Tại sao bạn bao gồm các tiêu đề hệ thống đầu tiên? Nó sẽ tốt hơn khác tại sao xung quanh vì quy tắc đầu tiên của bạn.
jhasse

Nếu bạn sử dụng Macro kiểm tra tính năng, bao gồm đầu tiên của bạn có lẽ KHÔNG nên là tiêu đề thư viện chuẩn. Do đó, về tổng quát, tôi muốn nói chính sách "địa phương trước, toàn cầu sau" là tốt nhất.
hmijail thương tiếc người từ chức

1
Về đề xuất đầu tiên của bạn về "không phụ thuộc vào người dùng", còn những tuyên bố chuyển tiếp trong tệp tiêu đề không cần bao gồm tệp tiêu đề thì sao? Chúng ta vẫn nên bao gồm tệp tiêu đề vì các khai báo chuyển tiếp đặt trách nhiệm cho người dùng tệp tiêu đề để bao gồm các tệp thích hợp.
Zoso

22

Để thêm gạch của riêng tôi vào tường.

  1. Mỗi tiêu đề cần phải tự cung cấp, chỉ có thể được kiểm tra nếu bao gồm ít nhất một lần đầu tiên
  2. Không nên sửa đổi ý nghĩa của tiêu đề bên thứ ba bằng cách giới thiệu các ký hiệu (macro, loại, v.v.)

Vì vậy, tôi thường đi như thế này:

// myproject/src/example.cpp
#include "myproject/example.h"

#include <algorithm>
#include <set>
#include <vector>

#include <3rdparty/foo.h>
#include <3rdparty/bar.h>

#include "myproject/another.h"
#include "myproject/specific/bla.h"

#include "detail/impl.h"

Mỗi nhóm được phân tách bằng một dòng trống từ nhóm tiếp theo:

  • Tiêu đề tương ứng với tệp cpp này trước tiên (kiểm tra độ tỉnh táo)
  • Tiêu đề hệ thống
  • Các tiêu đề của bên thứ ba, được tổ chức theo thứ tự phụ thuộc
  • Tiêu đề dự án
  • Dự án tư nhân

Cũng lưu ý rằng, ngoài các tiêu đề hệ thống, mỗi tệp nằm trong một thư mục có tên không gian tên của nó, chỉ vì việc theo dõi chúng theo cách này dễ dàng hơn.


2
Vì vậy, các tệp tiêu đề khác không bị ảnh hưởng bởi chúng. Cả hai theo những gì các tiêu đề hệ thống xác định (cả X bao gồm và Windows bao gồm đều xấu về việc #definelàm rối mã khác) và để ngăn chặn các phụ thuộc ngầm. Ví dụ: nếu tệp tiêu đề cơ sở mã của chúng tôi foo.hthực sự phụ thuộc vào <map>nhưng ở mọi nơi nó được sử dụng trong .cccác tệp, <map>tình cờ đã được đưa vào, có lẽ chúng tôi sẽ không nhận thấy. Cho đến khi ai đó cố gắng bao gồm foo.hmà không bao gồm đầu tiên <map>. Và rồi họ sẽ bực mình.

@ 0A0D: Vấn đề thứ hai không phải là vấn đề theo thứ tự ở đây, bởi vì mỗi vấn đề .hcó ít nhất một .cppvấn đề bao gồm nó trước (thực sự, trong mã cá nhân của tôi, bài kiểm tra Đơn vị liên quan bao gồm nó trước và mã nguồn bao gồm nó trong nhóm hợp pháp của nó ). Về việc không bị ảnh hưởng, nếu bất kỳ tiêu đề nào bao gồm <map>thì tất cả các tiêu đề bao gồm sau đó đều bị ảnh hưởng, vì vậy có vẻ như là một trận thua với tôi.
Matthieu M.

1
Tất nhiên, đó là lý do tại sao tôi thường xuyên đi xung quanh và sửa mã cũ hơn (hoặc thậm chí mã mới hơn), yêu cầu bao gồm không cần thiết vì nó chỉ tăng thời gian xây dựng.

@MatthieuM. Tôi muốn biết lý do đằng sau quan điểm của bạn tức là Header corresponding to this cpp file first (sanity check). Có điều gì đặc biệt nếu #include "myproject/example.h"được chuyển đến cuối của tất cả bao gồm?
MNS

1
@MNS: Một tiêu đề phải ở trạng thái tự do, nghĩa là không nên yêu cầu bao gồm bất kỳ tiêu đề nào khác trước nó. Trách nhiệm của bạn là người viết tiêu đề phải đảm bảo điều này và cách tốt nhất để làm điều đó là có một tệp nguồn trong đó tiêu đề này được đưa vào trước. Sử dụng tệp nguồn tương ứng với tệp tiêu đề rất dễ dàng, một lựa chọn tốt khác là sử dụng tệp nguồn kiểm tra đơn vị tương ứng với tệp tiêu đề nhưng nó ít phổ biến hơn (có thể không có kiểm tra đơn vị).
Matthieu M.

16

Tôi đề nghị:

  1. Tiêu đề cho mô-đun .cc bạn đang xây dựng. (Giúp đảm bảo mỗi tiêu đề trong dự án của bạn không có phụ thuộc ngầm định vào các tiêu đề khác trong dự án của bạn.)
  2. Tập tin hệ thống C.
  3. Tập tin hệ thống C ++.
  4. Nền tảng / HĐH / các tệp tiêu đề khác (ví dụ win32, gtk, openGL).
  5. Các tập tin tiêu đề khác từ dự án của bạn.

Và tất nhiên, thứ tự chữ cái trong mỗi phần, nếu có thể.

Luôn sử dụng khai báo chuyển tiếp để tránh #includes không cần thiết trong tệp tiêu đề của bạn.


+1, nhưng tại sao theo bảng chữ cái? Có vẻ như một cái gì đó có thể làm cho bạn cảm thấy tốt hơn, nhưng không có lợi ích thiết thực.
Bến

9
Bảng chữ cái là một thứ tự tùy ý, nhưng dễ dàng. Bạn không cần phải làm theo bảng chữ cái, nhưng bạn phải chọn một số thứ tự để mọi người thực hiện một cách nhất quán. Tôi đã tìm thấy nó giúp tránh trùng lặp và làm cho việc hợp nhất dễ dàng hơn. Và nếu bạn sử dụng văn bản cao siêu, F5 sẽ đặt hàng chúng cho bạn.
i_am_jorf

14

Tôi khá chắc chắn rằng đây không phải là một thực tiễn được đề xuất ở bất cứ đâu trong thế giới lành mạnh, nhưng tôi muốn hệ thống dòng bao gồm theo chiều dài tên tệp, được sắp xếp theo từ vựng trong cùng một độ dài. Thích như vậy:

#include <set>
#include <vector>
#include <algorithm>
#include <functional>

Tôi nghĩ rằng nên bao gồm các tiêu đề của riêng bạn trước các dân tộc khác, để tránh sự xấu hổ của sự phụ thuộc theo thứ tự bao gồm.


3
Tôi muốn sắp xếp các tiêu đề của mình bằng cách sử dụng một khóa bao gồm chữ cái thứ hai, thứ ba rồi chữ cái đầu tiên theo thứ tự đó :-) Vì vậy, vectơ, tập hợp, thuật toán, chức năng cho ví dụ của bạn.
paxdiablo

@paxdiablo, cảm ơn vì tiền boa. Tôi đang xem xét sử dụng nó, nhưng tôi lo ngại rằng nó có thể sẽ khiến cho đống tên tệp không ổn định và có khả năng bị lật. Ai biết những gì có thể được bao gồm nếu điều này xảy ra - thậm chí có thể windows.h.
clstrfsck

40
Sắp xếp theo chiều dài ? Chứng điên cuồng!
James McNellis

1
+1 cho lần đầu tiên. Nó thực sự có ý nghĩa, nếu bạn cần định vị trực quan các tiêu đề trong một tệp bằng mắt của bạn thì nó tốt hơn rất nhiều so với bảng chữ cái.
Kugel

6

Đây không phải là chủ quan. Hãy chắc chắn rằng các tiêu đề của bạn không dựa vào việc được #includetheo thứ tự cụ thể. Bạn có thể chắc chắn rằng việc bạn bao gồm các tiêu đề STL hoặc Boost không quan trọng.


1
Tôi đã giả định rằng không có sự phụ thuộc ngầm
Anycorn

Có, nhưng trình biên dịch không thể đưa ra giả định này, vì vậy #include <A>, <B> không bao giờ giống như #include <B>, <A> cho đến khi chúng được biên dịch.
Mikhail

4

Đầu tiên bao gồm tiêu đề tương ứng với .cpp ... nói cách khác, source1.cppnên bao gồm source1.htrước khi bao gồm mọi thứ khác. Ngoại lệ duy nhất tôi có thể nghĩ đến là khi sử dụng MSVC với các tiêu đề được biên dịch sẵn trong trường hợp đó, bạn buộc phải đưa vào stdafx.htrước mọi thứ khác.

Lý do: Bao gồm source1.htrước bất kỳ tệp nào khác đảm bảo rằng nó có thể độc lập mà không phụ thuộc vào nó. Nếu source1.hmất một phụ thuộc vào một ngày sau đó, trình biên dịch sẽ ngay lập tức cảnh báo bạn để thêm các khai báo chuyển tiếp cần thiết vào source1.h. Điều này lần lượt đảm bảo rằng các tiêu đề có thể được bao gồm trong bất kỳ thứ tự nào bởi những người phụ thuộc của họ.

Thí dụ:

nguồn1.h

class Class1 {
    Class2 c2;    // a dependency which has not been forward declared
};

nguồn1.cpp

#include "source1.h"    // now compiler will alert you saying that Class2 is undefined
                    // so you can forward declare Class2 within source1.h
...

Người dùng MSVC: Tôi thực sự khuyên bạn nên sử dụng các tiêu đề được biên dịch trước. Vì vậy, hãy chuyển tất cả các #includechỉ thị cho các tiêu đề tiêu chuẩn (và các tiêu đề khác sẽ không bao giờ thay đổi) sang stdafx.h.


2

Bao gồm từ cụ thể nhất đến ít cụ thể nhất, bắt đầu bằng .hpp tương ứng cho .cpp, nếu một trong số đó tồn tại. Bằng cách đó, mọi phụ thuộc ẩn trong các tệp tiêu đề không tự cung cấp sẽ được tiết lộ.

Điều này là phức tạp bởi việc sử dụng các tiêu đề được biên dịch trước. Một cách khác là, không làm cho trình biên dịch dự án của bạn cụ thể, là sử dụng một trong các tiêu đề dự án làm tiêu đề được biên dịch trước bao gồm tệp.


1

Đó là một câu hỏi khó trong thế giới C / C ++, với rất nhiều yếu tố vượt quá tiêu chuẩn.

Tôi nghĩ rằng thứ tự tập tin tiêu đề không phải là một vấn đề nghiêm trọng miễn là nó biên dịch, như squelart nói.

Ý tưởng của tôi là: Nếu không có xung đột biểu tượng trong tất cả các tiêu đề đó, mọi thứ tự đều ổn và vấn đề phụ thuộc tiêu đề có thể được khắc phục sau đó bằng cách thêm các dòng #incoide vào .h.

Rắc rối thực sự phát sinh khi một số tiêu đề thay đổi hành động của nó (bằng cách kiểm tra các điều kiện #if) theo các tiêu đề ở trên.

Ví dụ: trong stddef.h trong VS2005, có:

#ifdef  _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

Bây giờ vấn đề: Nếu tôi có một tiêu đề tùy chỉnh ("custom.h") cần được sử dụng với nhiều trình biên dịch, bao gồm một số trình biên dịch cũ không cung cấp offsetoftrong tiêu đề hệ thống của họ, tôi nên viết tiêu đề của mình:

#ifndef offsetof
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

Và hãy chắc chắn thông báo cho người dùng #include "custom.h" sau tất cả các tiêu đề hệ thống, nếu không, dòng offsetoftrong stddef.h sẽ xác nhận lỗi xác định lại macro.

Chúng tôi cầu nguyện không gặp bất kỳ trường hợp như vậy trong sự nghiệp của chúng tôi.

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.