Đặt câu lệnh bao gồm, tiêu đề hoặc nguồn ở đâu?


106

Tôi nên đặt bao gồm trong tệp tiêu đề hay tệp nguồn? Nếu tệp tiêu đề chứa các câu lệnh bao gồm, thì nếu tôi bao gồm tệp tiêu đề đó trong nguồn của mình, thì tệp nguồn của tôi có tất cả các tệp được bao gồm trong tiêu đề của tôi không? Hay tôi chỉ nên đưa chúng vào tệp nguồn của mình thôi?


2
Nhiều bản sao trước đó trên SO, ví dụ như nơi nên "bao gồm" được đặt trong C ++
Paul R

Câu trả lời:


141

Chỉ đặt bao gồm trong tiêu đề nếu chính tiêu đề đó cần chúng.

Ví dụ:

  • Hàm của bạn trả về kiểu size_t. Sau đó, #include <stddef.h>trong tệp tiêu đề .
  • Chức năng của bạn sử dụng strlen. Sau đó, #include <string.h>trong tệp nguồn .

2
Điều gì sẽ xảy ra nếu hàm của tôi nhận một đối số kiểu size_t?
andrybak

Câu hỏi tương tự mở rộng sang c ++: điều gì sẽ xảy ra nếu cấu trúc / lớp của tôi có một trường / thành viên kiểu size_thoặc std::string?
andrybak

10
Cơ sở lý luận là gì?
Patrizio Bertoni

Tôi có một tình huống có dây, lớp C ++ A có một đối tượng của một lớp B khác và tôi không thể sử dụng khai báo chuyển tiếp của B và kết thúc bao gồm tiêu đề B bên trong tiêu đề A. (sử dụng con trỏ không có vấn đề này)
shuva

@andrybak Tệp nguồn của bạn nên bao gồm tệp Tiêu đề của bạn để bất kỳ tệp nào bao gồm tiêu đề của bạn lấy nguồn của bạn cũng sẽ nhận được.
Jeremy Trifilo,

27

Có khá nhiều bất đồng về điều này trong những năm qua. Có một thời, tiêu đề chỉ khai báo những gì có trong bất kỳ mô-đun nào mà nó liên quan đến, vì vậy nhiều tiêu đề có các yêu cầu cụ thể khiến bạn phải có #includemột bộ tiêu đề nhất định (theo một thứ tự cụ thể). Một số lập trình viên C cực kỳ truyền thống vẫn theo mô hình này (về mặt tôn giáo, ít nhất là trong một số trường hợp).

Gần đây, có một phong trào hướng tới việc làm cho hầu hết các tiêu đề trở nên độc lập. Nếu tiêu đề đó yêu cầu thứ gì đó khác, thì tiêu đề tự xử lý điều đó, đảm bảo rằng bất kỳ thứ gì nó cần đều được đưa vào (theo đúng thứ tự, nếu có vấn đề về thứ tự). Cá nhân tôi thích điều này hơn - đặc biệt là khi thứ tự của các tiêu đề có thể quan trọng, nó giải quyết vấn đề một lần, thay vì yêu cầu tất cả những người sử dụng nó để giải quyết vấn đề một lần nữa.

Lưu ý rằng hầu hết các tiêu đề chỉ nên chứa các khai báo. Điều này có nghĩa là việc thêm một tiêu đề không cần thiết sẽ không (bình thường) có bất kỳ ảnh hưởng nào đến tệp thực thi cuối cùng của bạn. Điều tồi tệ nhất xảy ra là nó làm chậm quá trình biên dịch một chút.


2
Nếu tất cả các tiêu đề được viết theo kiểu thứ hai, sẽ không có vấn đề gì về thứ tự cả. Có vấn đề về thứ tự trong tiêu đề thường có nghĩa là bạn đã không đưa mọi thứ bạn cần vào tiêu đề.
Goodbye SE

12

Của bạn #includephải là tệp tiêu đề và mỗi tệp (nguồn hoặc tiêu đề) phải #includelà tệp tiêu đề mà nó cần. Tệp tiêu đề phải #includelà tệp tiêu đề tối thiểu cần thiết và tệp nguồn cũng phải, mặc dù nó không quan trọng đối với tệp nguồn.

Tệp nguồn sẽ có các tiêu đề của nó #includevà các tiêu đề của chúng #include, v.v. cho đến độ sâu lồng tối đa. Đây là lý do tại sao bạn không muốn #includecác s thừa trong tệp tiêu đề: chúng có thể khiến tệp nguồn bao gồm nhiều tệp tiêu đề mà nó có thể không cần, làm chậm quá trình biên dịch.

Điều này có nghĩa là hoàn toàn có khả năng các tệp tiêu đề có thể được đưa vào hai lần và đó có thể là một vấn đề. Phương pháp truyền thống là đặt "bao gồm bảo vệ" trong tệp tiêu đề, chẳng hạn như điều này cho tệp foo.h:

#ifndef INCLUDE_FOO_H
#define INCLUDE_FOO_H
/* everything in header goes here */
#endif

Tôi biết câu trả lời này rất cũ, nhưng kể từ đó họ đã thêm #pragma một lần, vì vậy bạn không cần bao gồm #ifndef khi khai báo #includes Tôi đã đăng câu trả lời này cũ hơn nhưng phổ biến hơn / chủ đề được ủng hộ có xu hướng ở đầu tìm kiếm của google
Chó săn Dogunbound

6

Cách tiếp cận mà tôi đã phát triển trong hơn hai mươi năm là thế này;

Xem xét một thư viện.

Có nhiều tệp C, một tệp H bên trong và một tệp H bên ngoài. Các tệp C bao gồm tệp H bên trong. Tệp H bên trong bao gồm tệp H bên ngoài.

Bạn thấy rằng từ trình biên dịch POV, khi nó biên dịch tệp C, có một hệ thống phân cấp;

bên ngoài -> nội bộ -> mã C

Đây là thứ tự chính xác, vì bên ngoài là mọi thứ mà bên thứ ba cần để sử dụng thư viện. Đó là nội bộ được yêu cầu để biên dịch mã C.


4

Nếu tập tin header Một #includestiêu đề tập tin B và C, sau đó mỗi tập tin nguồn mà #includesA sẽ cũng có được B và C #included. Bộ xử lý trước theo nghĩa đen chỉ thực hiện thay thế văn bản: bất cứ nơi nào nó tìm thấy văn bản cho biết #include <foo.h>nó thay thế nó bằng văn bản của foo.htệp.

Có nhiều ý kiến ​​khác nhau về việc bạn nên đặt #includestiêu đề hay tệp nguồn. Cá nhân, tôi thích đặt tất cả #includestrong tệp nguồn theo mặc định, nhưng bất kỳ tệp tiêu đề nào không thể biên dịch mà không có các tiêu đề tiên quyết khác nên chính #includecác tiêu đề đó.

Và mọi tệp tiêu đề nên chứa một bảo vệ bao gồm để ngăn nó được đưa vào nhiều lần.


4

Trong một số môi trường, việc biên dịch sẽ nhanh nhất nếu một môi trường chỉ bao gồm các tệp tiêu đề mà người ta cần. Trong các môi trường khác, quá trình biên dịch sẽ được tối ưu hóa nếu tất cả các tệp nguồn có thể sử dụng cùng một bộ sưu tập tiêu đề chính (một số tệp có thể có các tiêu đề bổ sung ngoài tập hợp con chung). Tốt nhất, tiêu đề nên được tạo để nhiều thao tác #include sẽ không có tác dụng. Có thể tốt nếu bao quanh các câu lệnh #include với các kiểm tra đối với trình bảo vệ của tệp-được-bao gồm, mặc dù điều đó tạo ra sự phụ thuộc vào định dạng của trình bảo vệ đó. Hơn nữa, tùy thuộc vào hành vi lưu vào bộ nhớ đệm tệp của hệ thống, một #include không cần thiết có mục tiêu kết thúc hoàn toàn là # ifdef'ed có thể không mất nhiều thời gian.

Một điều khác cần xem xét là nếu một hàm nhận một con trỏ đến một cấu trúc, người ta có thể viết nguyên mẫu dưới dạng

void foo (struct BAR_s * bar);

mà không có định nghĩa cho BAR_s phải nằm trong phạm vi. Một cách tiếp cận rất tiện dụng để tránh bao gồm không cần thiết.

Tái bút - trong nhiều dự án của tôi, sẽ có một tệp mà người ta mong đợi rằng mọi mô-đun sẽ #include, chứa những thứ như typedef cho kích thước số nguyên và một vài cấu trúc và sự kết hợp phổ biến [ví dụ:

typedef liên minh {
  không dấu dài l;
  không dấu ngắn lw [2];
  không dấu char lb [4];
} U_QUAD;

(Vâng, tôi biết mình sẽ gặp rắc rối nếu chuyển sang kiến ​​trúc big-endian, nhưng vì trình biên dịch của tôi không cho phép các cấu trúc ẩn danh trong các liên minh, nên việc sử dụng các mã nhận dạng được đặt tên cho các byte trong liên hợp sẽ yêu cầu chúng được truy cập như theUnion.b.b1, v.v. có vẻ khá khó chịu.


3

Tạo tất cả các tệp của bạn để chúng có thể được tạo chỉ bằng những gì chúng bao gồm. Nếu bạn không cần bao gồm trong tiêu đề của mình, hãy xóa nó. Trong một dự án lớn nếu bạn không duy trì kỷ luật này, bạn có thể tự phá vỡ toàn bộ bản dựng khi ai đó xóa phần bao gồm khỏi tệp tiêu đề đang được người tiêu dùng sử dụng tệp đó và thậm chí không phải bởi tiêu đề.


1

Tệp nguồn của bạn sẽ có các câu lệnh bao gồm nếu bạn đặt nó trong tiêu đề. Tuy nhiên, trong một số trường hợp, sẽ tốt hơn nếu đưa chúng vào tệp nguồn.

Hãy nhớ rằng nếu bạn đưa tiêu đề đó vào bất kỳ nguồn nào khác, chúng cũng sẽ nhận được bao gồm từ tiêu đề và điều đó không phải lúc nào cũng mong muốn. Bạn chỉ nên bao gồm những thứ mà nó được sử dụng.


1

Bạn chỉ nên đưa các tệp vào tiêu đề mà bạn cần khai báo hằng số và khai báo hàm. Về mặt kỹ thuật, những bao gồm này cũng sẽ được bao gồm trong tệp nguồn của bạn, nhưng để rõ ràng, bạn chỉ nên đưa vào mỗi tệp những tệp bạn thực sự cần sử dụng. Do đó, bạn cũng nên bảo vệ chúng trong tiêu đề của mình khỏi nhiều lần đưa vào:

#ifndef NAME_OF_HEADER_H
#define NAME_OF_HEADER_H

...definition of header file...

#endif

Điều này ngăn tiêu đề được đưa vào nhiều lần, dẫn đến lỗi trình biên dịch.

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.