Những gì nên và những gì không nên có trong một tập tin tiêu đề? [đóng cửa]


71

Những điều tuyệt đối không bao giờ nên được bao gồm trong một tập tin tiêu đề?

Ví dụ, nếu tôi đang làm việc với một định dạng tiêu chuẩn công nghiệp có nhiều hằng số, thì có phải là một cách thực hành tốt để xác định chúng trong tệp tiêu đề (nếu tôi đang viết một trình phân tích cú pháp cho định dạng đó)?

Những chức năng nào nên đi vào tập tin tiêu đề?
Những chức năng nào không nên?


1
Ngắn và không đau: định nghĩa và khai báo cần thiết trong nhiều hơn một mô-đun.
ott--

21
Đánh dấu câu hỏi này là "quá rộng" và kết thúc là một sự quá mức đáng xấu hổ tuyệt đối của kiểm duyệt. Câu hỏi này hỏi chính xác những gì tôi đang tìm kiếm - Câu hỏi được hình thành rõ ràng và hỏi một câu hỏi rất rõ ràng: thực tiễn tốt nhất là gì? Nếu điều này là "quá rộng" cho việc xây dựng phần mềm .. chúng ta có thể đóng cửa toàn bộ diễn đàn này.
Gewure

TL; DR. Đối với C ++, trong phiên bản thứ tư của "Ngôn ngữ lập trình C ++" được viết bởi Bjarne Stroustrup (người tạo ra nó), trong Phần 15.2.2, nó được mô tả những gì một tiêu đề nên và không nên chứa. Tôi biết bạn đã gắn thẻ câu hỏi cho C, nhưng một số lời khuyên cũng được áp dụng. Tôi nghĩ rằng đây là một câu hỏi hay ...
horro

Câu trả lời:


57

Những gì cần đặt trong tiêu đề:

  • Tập hợp các lệnh tối thiểu #includecần thiết để làm cho tiêu đề có thể biên dịch được khi tiêu đề được bao gồm trong một số tệp nguồn.
  • Định nghĩa biểu tượng tiền xử lý của những thứ cần được chia sẻ và chỉ có thể được thực hiện thông qua bộ tiền xử lý. Ngay cả trong C, các ký hiệu tiền xử lý vẫn được giữ ở mức tối thiểu.
  • Khai báo chuyển tiếp của các cấu trúc cần thiết để tạo các định nghĩa cấu trúc, nguyên mẫu hàm và khai báo biến toàn cục trong phần thân của tiêu đề có thể biên dịch được.
  • Các định nghĩa về cấu trúc dữ liệu và bảng liệt kê được chia sẻ giữa nhiều tệp nguồn.
  • Khai báo cho các hàm và các biến có định nghĩa sẽ hiển thị cho trình liên kết.
  • Định nghĩa hàm nội tuyến, nhưng hãy cẩn thận ở đây.

Những gì không thuộc về một tiêu đề:

  • #includeChỉ thị vô cớ . Những điều vô cớ đó bao gồm việc biên dịch lại những thứ không cần phải biên dịch lại, và đôi khi có thể khiến nó trở thành một hệ thống không thể biên dịch được. Không #includetập tin trong tiêu đề nếu bản thân tiêu đề không cần tập tin tiêu đề khác.
  • Các ký hiệu tiền xử lý mà ý định của chúng có thể được thực hiện bằng một số cơ chế, bất kỳ cơ chế nào, ngoài cơ chế tiền xử lý.
  • Rất nhiều và rất nhiều định nghĩa cấu trúc. Chia chúng thành các tiêu đề riêng biệt.
  • Định nghĩa nội tuyến của các hàm yêu cầu bổ sung #include, có thể thay đổi hoặc quá lớn. Các hàm nội tuyến đó sẽ có rất ít nếu có bất kỳ quạt nào ra ngoài và nếu chúng có quạt ra, thì nó nên được định vị thành các thứ được xác định trong tiêu đề.

Điều gì tạo thành tập hợp tối thiểu của #includebáo cáo?

Điều này hóa ra là một câu hỏi không cần thiết. Định nghĩa TL; DR: Tệp tiêu đề phải bao gồm các tệp tiêu đề xác định trực tiếp từng loại được sử dụng trực tiếp hoặc khai báo trực tiếp từng hàm được sử dụng trong tệp tiêu đề đang đề cập, nhưng không được bao gồm bất kỳ thứ gì khác. Một loại tham chiếu con trỏ hoặc C ++ không đủ điều kiện là sử dụng trực tiếp; tài liệu tham khảo về phía trước được ưa thích.

Có một nơi cho một #includechỉ thị vô cớ , và đây là một thử nghiệm tự động. Đối với mỗi tệp tiêu đề trong gói phần mềm, tôi sẽ tự động tạo và sau đó biên dịch các mục sau:

#include "path/to/random/header_under_test"
int main () { return 0; }

Việc biên dịch phải sạch sẽ (nghĩa là không có bất kỳ cảnh báo hay lỗi nào). Các cảnh báo hoặc lỗi liên quan đến các loại không đầy đủ hoặc các loại không xác định có nghĩa là tệp tiêu đề được kiểm tra có một số #includechỉ thị bị thiếu và / hoặc thiếu các khai báo chuyển tiếp. Lưu ý rõ: Chỉ vì bài kiểm tra vượt qua không có nghĩa là tập hợp các #includechỉ thị là đủ, chứ đừng nói là tối thiểu.


Vì vậy, nếu tôi có một thư viện xác định cấu trúc, được gọi là A và thư viện này, được gọi là B, sử dụng cấu trúc đó và thư viện B được sử dụng bởi chương trình C, tôi nên đưa tệp tiêu đề của thư viện A vào tiêu đề chính của thư viện B, hoặc nên Tôi chỉ chuyển tiếp tuyên bố nó? thư viện A được biên dịch và liên kết với thư viện B trong quá trình biên dịch.
MarcusJ

@MarcusJ - Điều đầu tiên tôi liệt kê trong phần Không thuộc về tiêu đề là các câu lệnh #include vô cớ. Nếu tệp tiêu đề B không phụ thuộc vào các định nghĩa trong tệp tiêu đề A, đừng #incolee tệp tiêu đề A trong tệp tiêu đề B. Tệp tiêu đề không phải là nơi để chỉ định phụ thuộc của bên thứ ba hoặc hướng dẫn xây dựng. Những người đi đâu đó như một tập tin readme cấp cao nhất.
David Hammen

1
@MarcusJ - Tôi đã cập nhật câu trả lời của mình trong nỗ lực trả lời câu hỏi của bạn. Lưu ý rằng không có một câu trả lời cho câu hỏi của bạn. Tôi sẽ minh họa với một vài thái cực. Trường hợp 1: Nơi duy nhất mà thư viện B sử dụng trực tiếp chức năng của thư viện A là trong các tệp nguồn của thư viện B. Trường hợp 2: Thư viện B là phần mở rộng mỏng của chức năng trong thư viện A, với (các) tệp tiêu đề cho thư viện B trực tiếp sử dụng các loại và / hoặc các chức năng được xác định trong thư viện A. Trong trường hợp 1, không có lý do nào để lộ thư viện A trong (các) tiêu đề cho thư viện B. Trong trường hợp 2, việc tiếp xúc này là bắt buộc khá nhiều.
David Hammen

Vâng, đó là trường hợp 2, xin lỗi, nhận xét của tôi đã bỏ qua thực tế là nó sử dụng các loại được khai báo trong thư viện A trong tiêu đề của thư viện B, tôi đã nghĩ rằng tôi có thể chuyển tiếp khai báo nó, nhưng tôi không nghĩ rằng nó sẽ hoạt động. Cảm ơn các cập nhật.
MarcusJ

Việc thêm các hằng số vào một tệp tiêu đề có phải là không không?
mding5692

15

Ngoài những gì đã được nói.

Các tệp H phải luôn chứa:

  • Tài liệu mã nguồn !!! Ở mức tối thiểu, mục đích của các tham số khác nhau và giá trị trả về của các hàm là gì.
  • Bảo vệ tiêu đề, #ifndef MYHEADER_H #define MYHEADER_H ... #endif

Không bao giờ chứa tệp H:

  • Bất kỳ hình thức phân bổ dữ liệu.
  • Định nghĩa hàm. Hàm nội tuyến có thể là một ngoại lệ hiếm trong một số trường hợp.
  • Bất cứ điều gì được dán nhãn static.
  • Typedefs, #defines hoặc hằng số không liên quan đến phần còn lại của ứng dụng.

(Tôi cũng sẽ nói rằng không bao giờ có bất kỳ lý do nào để sử dụng các biến toàn cục / bên ngoài không đổi, ở bất cứ đâu, nhưng đó là một cuộc thảo luận cho một bài đăng khác.)


1
Tôi đồng ý với tất cả mọi thứ, ngoại trừ những gì bạn đã nhấn mạnh. Nếu bạn đang làm một thư viện, vâng, bạn nên ghi lại tài liệu hoặc người dùng thư viện của bạn. Đối với một dự án nội bộ, bạn không cần phải làm lộn xộn các tiêu đề của mình với tài liệu, nếu bạn sử dụng các tên hàm và biến tự giải thích tốt.
martiert

5
@martiert Tôi cũng thuộc trường phái "hãy để mã tự nói". Tuy nhiên, ít nhất bạn nên luôn luôn ghi lại các chức năng của mình, ngay cả khi không ai ngoài chính bạn sẽ sử dụng chúng. Điều đặc biệt quan tâm là: trong trường hợp hàm có xử lý lỗi, nó sẽ trả về mã lỗi nào và trong điều kiện nào thì nó không thành công? Điều gì xảy ra với các tham số (bộ đệm, con trỏ, v.v.) nếu chức năng không thành công? Một điều rất có liên quan là: các tham số con trỏ trả lại một cái gì đó cho người gọi, tức là họ có mong đợi bộ nhớ được phân bổ không? ->

1
Rõ ràng với người gọi những gì xử lý lỗi được thực hiện bên trong chức năng và những gì không được thực hiện. Nếu hàm mong đợi một bộ đệm được phân bổ, rất có thể nó sẽ để lại các kiểm tra ngoài giới hạn cho người gọi. Nếu hàm phụ thuộc vào một chức năng khác sẽ được thực thi, thì điều này phải được ghi lại (tức là chạy link_list_init () trước link_list_add ()). Và cuối cùng, nếu chức năng có "tác dụng phụ" như tạo tệp, luồng, bộ hẹn giờ hoặc bất cứ thứ gì, thì nó phải được nêu trong tài liệu. ->

1
Có lẽ "tài liệu mã nguồn" ở đây quá rộng, điều này thực sự thuộc về mã nguồn. "Tài liệu sử dụng" với đầu vào và đầu ra, trước và sau điều kiện và các tác dụng phụ chắc chắn phải có ở đó, không phải trong sử thi mà ở dạng ngắn gọn .
Bảo mật

2
Một chút muộn màng, nhưng +1 cho tài liệu. Tại sao lớp này tồn tại? Mã không nói cho chính nó. Chức năng này làm gì? RTFC (đọc tệp .cpp tốt) là từ viết tắt bốn chữ cái tục tĩu. Một người không bao giờ phải RTFC để hiểu. Nguyên mẫu trong tiêu đề nên tóm tắt, trong một số nhận xét có thể trích xuất (ví dụ, doxygen), các đối số là gì và hàm làm gì. Tại sao thành viên dữ liệu này tồn tại, nó chứa những gì và là giá trị tính bằng mét, feet hoặc furlongs? Đó cũng là một chủ đề khác cho ý kiến ​​(trích xuất) tồn tại.
David Hammen

4

Tôi có lẽ sẽ không bao giờ nói không bao giờ, nhưng các câu lệnh tạo dữ liệu và mã khi chúng được phân tích cú pháp không nên nằm trong tệp .h.

Macro, hàm và mẫu nội tuyến có thể trông giống như dữ liệu hoặc mã, nhưng chúng không tạo mã khi chúng được phân tích cú pháp, mà thay vào đó khi chúng được sử dụng. Các mục này thường cần được sử dụng trong nhiều .c hoặc .cpp, vì vậy chúng thuộc về .h.

Theo quan điểm của tôi, một tệp tiêu đề nên có giao diện thực tế tối thiểu với .c hoặc .cpp tương ứng. Giao diện có thể bao gồm #defines, class, typedef, định nghĩa cấu trúc, nguyên mẫu hàm và các định nghĩa bên ngoài ít được ưa thích hơn cho các biến toàn cục. Tuy nhiên, nếu một khai báo chỉ được sử dụng trong một tệp nguồn, thì có lẽ nên loại trừ nó khỏi .h và được chứa trong tệp nguồn thay thế.

Một số có thể không đồng ý, nhưng tiêu chí cá nhân của tôi đối với các tệp .h là chúng # bao gồm tất cả các tệp .h khác mà chúng cần để có thể biên dịch. Trong một số trường hợp, đây có thể là rất nhiều tệp, vì vậy chúng tôi có một số phương pháp hiệu quả để giảm sự phụ thuộc bên ngoài như khai báo chuyển tiếp đến các lớp cho phép chúng tôi sử dụng con trỏ tới các đối tượng của lớp mà không bao gồm những gì có thể là một cây bao gồm các tệp lớn.


3

Tập tin tiêu đề nên có tổ chức sau:

  • định nghĩa kiểu và hằng
  • khai báo đối tượng bên ngoài
  • khai báo hàm ngoài

Các tệp tiêu đề không bao giờ nên chứa các định nghĩa đối tượng, chỉ các định nghĩa kiểu và khai báo đối tượng.


Những gì về định nghĩa hàm nội tuyến?
Kos

Nếu chức năng nội tuyến là một chức năng của người trợ giúp trực tuyến, chỉ sử dụng bên trong một mô-đun C, thì chỉ đặt nó trong tệp .c đó. Nếu, chức năng nội tuyến phải được hiển thị cho hai hoặc nhiều mô-đun, hãy đặt nó bên trong tệp tiêu đề.
theD

Ngoài ra, nếu chức năng phải được hiển thị trên một ranh giới thư viện, đừng biến nó thành nội tuyến vì điều đó buộc mọi người sử dụng thư viện phải biên dịch lại mỗi khi bạn sửa đổi mọi thứ.
Donal Fellows

@DonalFellows: Đó là một giải pháp trái tay. Một quy tắc tốt hơn: Đừng đặt nội dung vào các tiêu đề có thể sửa đổi thường xuyên. Không có gì sai khi nội tuyến một hàm nhỏ trong tiêu đề nếu hàm không có fanout và có định nghĩa rõ ràng sẽ chỉ thay đổi nếu cấu trúc dữ liệu cơ bản thay đổi. Nếu định nghĩa hàm thay đổi vì định nghĩa cấu trúc cơ bản thay đổi, vâng, bạn phải biên dịch lại mọi thứ, nhưng dù sao bạn cũng sẽ phải làm điều đó vì định nghĩa cấu trúc đã thay đổi.
David Hammen

0

Các câu lệnh tạo dữ liệu và mã khi chúng được phân tích cú pháp, không nên có trong một .htệp. Theo quan điểm của tôi, một tệp tiêu đề chỉ nên có giao diện thực tế tối thiểu tương ứng .choặc .cpp.

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.