Những gì nên đi vào một tệp .h?


93

Khi chia mã của bạn thành nhiều tệp, chính xác thì những gì sẽ chuyển vào tệp .h và những gì sẽ chuyển vào tệp .cpp?



7
Đây là một vấn đề về kiểu thuần túy, nhưng tôi tin rằng các khai báo C ++ đi trong một .hpptệp trong khi các khai báo C đi vào một .htệp. Điều này rất hữu ích khi trộn mã C và C ++ (ví dụ: các mô-đun kế thừa trong C).
Thomas Matthews

@ThomasMatthews Có lý. Đó là thực hành của nó được sử dụng?
ty

@lightningleaf: Có, thông lệ được sử dụng đặc biệt khi trộn ngôn ngữ C ++ và C.
Thomas Matthews

Câu trả lời:


113

Tệp tiêu đề ( .h) được thiết kế để cung cấp thông tin cần thiết trong nhiều tệp. Những thứ như khai báo lớp, nguyên mẫu hàm và liệt kê thường nằm trong tệp tiêu đề. Trong một từ, "định nghĩa".

Tệp mã ( .cpp) được thiết kế để cung cấp thông tin triển khai chỉ cần biết trong một tệp. Nói chung, các thân hàm và các biến bên trong không bao giờ được các mô-đun khác truy cập, là những gì thuộc về .cpptệp. Nói một cách dễ hiểu, "triển khai".

Câu hỏi đơn giản nhất để tự hỏi mình để xác định những gì thuộc về đâu là "nếu tôi thay đổi điều này, tôi có phải thay đổi mã trong các tệp khác để làm cho mọi thứ được biên dịch lại không?" Nếu câu trả lời là "có", nó có thể nằm trong tệp tiêu đề; nếu câu trả lời là "không" nó có thể thuộc về tệp mã.


4
Ngoại trừ dữ liệu lớp riêng phải đi vào tiêu đề. Các mẫu phải được hoàn toàn xác định tiêu đề (trừ khi bạn sử dụng một trong số ít trình biên dịch hỗ trợ export). Cách duy nhất xung quanh # 1 là PIMPL. # 2 sẽ khả thi nếu exportđược hỗ trợ và có thể sử dụng c ++ 0x và các externmẫu. IMO, các tệp tiêu đề trong c ++ mất nhiều tính hữu ích.
KitsuneYMG 22/12/09

23
Tất cả đều tốt, nhưng với thuật ngữ không chính xác. Trong một từ, "tuyên bố" - thuật ngữ "định nghĩa" đồng nghĩa với "thực hiện". Chỉ mã khai báo, mã nội tuyến, định nghĩa macro và mã mẫu mới được ở trong tiêu đề; tức là không có gì khởi tạo mã hoặc dữ liệu.
Clifford

8
Tôi phải đồng ý với Clifford. Bạn sử dụng khai báo và định nghĩa thuật ngữ khá lỏng lẻo và có thể hoán đổi cho nhau. Nhưng chúng có ý nghĩa chính xác trong C ++. Ví dụ: Một khai báo lớp giới thiệu tên của một lớp nhưng không cho biết có gì trong đó. Một định nghĩa lớp liệt kê tất cả các thành viên và chức năng bạn bè. Cả hai đều có thể được đưa vào tệp tiêu đề mà không gặp vấn đề gì. Cái bạn gọi là "nguyên mẫu hàm" là một khai báo hàm . Nhưng một định nghĩa về hàm là thứ chứa mã của hàm và phải được đặt vào tệp cpp - trừ khi nó nằm trong dòng hoặc (một phần của) một mẫu.
sellibitze

5
Chúng có nghĩa chính xác trong C ++, chúng không có nghĩa chính xác trong tiếng Anh. Câu trả lời của tôi đã được viết trong phần sau.
Amber

54

Thực tế là, trong C ++, điều này có phần phức tạp hơn tổ chức tiêu đề / nguồn C.

Trình biên dịch thấy gì?

Trình biên dịch thấy một tệp nguồn lớn (.cpp) với các tiêu đề của nó được đưa vào đúng cách. Tệp nguồn là đơn vị biên dịch sẽ được biên dịch thành tệp đối tượng.

Vì vậy, tại sao tiêu đề lại cần thiết?

Bởi vì một đơn vị biên dịch có thể cần thông tin về việc triển khai trong một đơn vị biên dịch khác. Vì vậy, người ta có thể viết ví dụ về việc thực hiện một hàm trong một nguồn và viết phần khai báo của hàm này trong một nguồn khác cần sử dụng nó.

Trong trường hợp này, có hai bản sao của cùng một thông tin. Cái nào ác ...

Giải pháp là chia sẻ một số chi tiết. Trong khi việc triển khai vẫn ở trong Nguồn, việc khai báo các ký hiệu được chia sẻ, như hàm hoặc định nghĩa cấu trúc, lớp, enum, v.v., có thể cần được chia sẻ.

Tiêu đề được sử dụng để đặt những chi tiết được chia sẻ.

Di chuyển đến tiêu đề các khai báo về những gì cần được chia sẻ giữa nhiều nguồn

Chỉ có bấy nhiêu thôi?

Trong C ++, có một số thứ khác có thể được đưa vào tiêu đề vì chúng cũng cần được chia sẻ:

  • mã nội tuyến
  • mẫu
  • hằng số (thường là những hằng số bạn muốn sử dụng bên trong công tắc ...)

Di chuyển đến tiêu đề MỌI THỨ những gì cần được chia sẻ, bao gồm cả các triển khai được chia sẻ

Sau đó, nó có nghĩa là có thể có các nguồn bên trong các tiêu đề?

Đúng. Trên thực tế, có rất nhiều thứ khác nhau có thể nằm trong một "tiêu đề" (tức là được chia sẻ giữa các nguồn).

  • Chuyển tiếp khai báo
  • khai báo / định nghĩa hàm / cấu trúc / lớp / mẫu
  • triển khai mã nội tuyến và mã mẫu

Nó trở nên phức tạp và trong một số trường hợp (phụ thuộc vòng tròn giữa các ký hiệu), không thể giữ nó trong một tiêu đề.

Tiêu đề có thể được chia thành ba phần

Điều này có nghĩa là, trong trường hợp cực đoan, bạn có thể có:

  • một tiêu đề khai báo chuyển tiếp
  • một tiêu đề khai báo / định nghĩa
  • một tiêu đề triển khai
  • một nguồn thực hiện

Hãy tưởng tượng chúng ta có một MyObject được tạo mẫu. Chúng ta có thể có:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Chà!

Trong "cuộc sống thực", nó thường ít phức tạp hơn. Hầu hết mã sẽ chỉ có tiêu đề / tổ chức nguồn đơn giản, với một số mã nội tuyến trong nguồn.

Nhưng trong các trường hợp khác (các đối tượng được tạo khuôn mẫu biết nhau), tôi phải có các tiêu đề khai báo và triển khai riêng cho từng đối tượng, với một nguồn trống bao gồm các tiêu đề đó chỉ để giúp tôi xem một số lỗi biên dịch.

Một lý do khác để chia nhỏ các tiêu đề thành các tiêu đề riêng biệt có thể là để tăng tốc quá trình biên dịch, hạn chế số lượng ký hiệu được phân tích cú pháp ở mức nghiêm ngặt cần thiết và tránh biên dịch lại không cần thiết đối với một nguồn chỉ quan tâm đến khai báo chuyển tiếp khi việc triển khai phương thức nội tuyến thay đổi.

Phần kết luận

Bạn nên làm cho tổ chức mã của mình đơn giản nhất có thể và theo mô-đun càng tốt. Đặt càng nhiều càng tốt vào tệp nguồn. Chỉ hiển thị trong tiêu đề những gì cần được chia sẻ.

Nhưng một ngày bạn sẽ có sự phụ thuộc vòng tròn giữa các đối tượng được tạo mẫu, đừng ngạc nhiên nếu tổ chức mã của bạn trở nên "thú vị" hơn khi tổ chức tiêu đề / nguồn đơn giản ...

^ _ ^


17

ngoài tất cả các câu trả lời khác, tôi sẽ cho bạn biết những gì bạn KHÔNG ĐƯỢC đặt trong tệp tiêu đề:
usingkhai báo (phổ biến nhất using namespace std;) không nên xuất hiện trong tệp tiêu đề vì chúng làm ô nhiễm không gian tên của tệp nguồn mà nó được bao gồm .


+1 với một lời cảnh báo mà bạn có thể thực hiện bằng cách sử dụng miễn là nó nằm trong một số vùng tên chi tiết (hoặc một vùng tên ẩn danh). Nhưng có, đừng bao giờ sử dụng usingđể đưa nội dung vào không gian tên chung trong tiêu đề.
KitsuneYMG 22/12/09

+1 Câu này dễ trả lời hơn nhiều. :) Ngoài ra, các tệp tiêu đề không được chứa các không gian tên ẩn danh .
sellibitze

Các tệp tiêu đề chứa không gian tên ẩn danh cũng được, miễn là bạn hiểu điều đó có nghĩa là gì, tức là mỗi đơn vị dịch sẽ có một bản sao khác nhau của nội dung bạn xác định không gian tên. Các hàm nội tuyến trong không gian tên ẩn danh được khuyến nghị trong C ++ cho các trường hợp bạn sử dụng static inlinetrong C99, vì điều gì đó liên quan đến những gì xảy ra khi bạn kết hợp liên kết nội bộ với các mẫu. Không gian tên Anon cho phép bạn "ẩn" các chức năng, trong khi vẫn duy trì liên kết bên ngoài.
Steve Jessop

Steve, những gì bạn viết không thuyết phục tôi. Vui lòng chọn một ví dụ cụ thể mà bạn cho rằng không gian tên anon hoàn toàn có ý nghĩa trong tệp tiêu đề.
sellibitze

6

Những gì biên dịch thành hư không (dấu chân nhị phân không) sẽ đi vào tệp tiêu đề.

Các biến không biên dịch thành hư không, nhưng khai báo kiểu thì có (vì chúng chỉ mô tả cách các biến hoạt động).

các hàm thì không, nhưng các hàm nội tuyến thì có (hoặc macro), bởi vì chúng chỉ tạo ra mã khi được gọi.

các mẫu không phải là mã, chúng chỉ là công thức để tạo mã. vì vậy chúng cũng đi trong tệp h.


1
"các hàm nội tuyến ... chỉ tạo mã khi được gọi". Đo không phải sự thật. Các hàm nội tuyến có thể có hoặc không nội tuyến tại các trang web gọi, nhưng ngay cả khi chúng được nội dòng, thân hàm thực vẫn tồn tại giống như đối với một hàm không nội tuyến. Lý do có thể có các hàm nội tuyến trong tiêu đề không liên quan gì đến việc chúng có tạo mã hay không, đó là bởi vì các hàm nội tuyến không kích hoạt một quy tắc định nghĩa, vì vậy không giống như các hàm không nội tuyến, không có vấn đề gì với việc liên kết hai đơn vị dịch khác nhau với nhau cả hai đều bao gồm tiêu đề.
Steve Jessop

3

Nói chung, bạn đặt khai báo trong tệp tiêu đề và định nghĩa trong tệp triển khai (.cpp). Ngoại lệ đối với điều này là các mẫu, trong đó định nghĩa cũng phải đi trong tiêu đề.

Câu hỏi này và những câu hỏi tương tự đã được hỏi thường xuyên trên SO - hãy xem Tại sao có tệp tiêu đề và tệp .cpp trong C ++? Tệp tiêu đề C ++, Tách mã chẳng hạn.


tất nhiên, bạn cũng có thể đặt các định nghĩa lớp vào các tệp tiêu đề. Chúng thậm chí không cần phải là khuôn mẫu.
sellibitze

1

Các khai báo lớp và hàm của bạn cùng với tài liệu và định nghĩa cho các hàm / phương thức nội tuyến (mặc dù một số thích đặt chúng trong các tệp .inl riêng biệt).


1

Tệp tiêu đề chủ yếu chứa khung lớp hoặc khai báo (không thay đổi thường xuyên)

và tệp cpp chứa triển khai lớp (thay đổi thường xuyên).


5
Vui lòng tránh sử dụng thuật ngữ không chuẩn. "Khung lớp", "triển khai lớp" là gì? Ngoài ra, những gì bạn gọi là khai báo trong ngữ cảnh của các lớp có thể bao gồm các định nghĩa lớp.
sellibitze

0

tệp tiêu đề (.h) phải dành cho khai báo các lớp, cấu trúc và các phương thức, nguyên mẫu của nó, v.v. Việc triển khai các đối tượng đó được thực hiện trong cpp.

trong .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0

Tôi mong đợi sẽ thấy:

  • tuyên bố
  • bình luận
  • định nghĩa được đánh dấu nội tuyến
  • mẫu

câu trả lời thực sự là những gì không nên đưa vào:

  • definitons (có thể dẫn đến những thứ được nhân lên được xác định)
  • bằng cách sử dụng các khai báo / lệnh (buộc chúng trên bất kỳ ai bao gồm cả tiêu đề của bạn, có thể gây ra dấu gạch ngang tên)

1
Bạn chắc chắn cũng có thể đưa các định nghĩa lớp vào các tệp tiêu đề. Một khai báo lớp không nói gì về các thành viên của nó.
sellibitze

0

Tiêu đề Xác định một cái gì đó nhưng không cho biết bất cứ điều gì về việc triển khai. (Loại trừ các Mẫu trong "metafore" này.

Như đã nói, bạn cần phải chia "định nghĩa" thành các nhóm phụ, trong trường hợp này, có hai loại định nghĩa.

  • Bạn xác định "bố cục" của strucutre của mình, chỉ cho biết các nhóm sử dụng xung quanh càng nhiều càng tốt.
  • Các định nghĩa của một biến, hàm và một lớp.

Bây giờ, tất nhiên tôi đang nói về nhóm con đầu tiên.

Tiêu đề ở đó để xác định bố cục của cấu trúc của bạn để giúp phần còn lại của phần mềm sử dụng việc triển khai. Bạn có thể muốn xem nó như một "sự trừu tượng hóa" của việc triển khai của bạn, điều này được nói một cách khoa trương nhưng tôi nghĩ nó khá phù hợp trong trường hợp này.

Như các áp phích trước đã nói và cho thấy bạn khai báo các khu vực sử dụng riêng tư và công cộng và tiêu đề của chúng, điều này cũng bao gồm các biến riêng tư và công khai. Bây giờ, tôi không muốn đi vào thiết kế mã ở đây, nhưng bạn có thể muốn xem xét những gì bạn đặt trong tiêu đề của mình, vì đó là Lớp giữa người dùng cuối và việc triển khai.


0
  • Tệp tiêu đề - không nên thay đổi quá thường xuyên trong quá trình phát triển -> bạn nên suy nghĩ và viết chúng ngay lập tức (trong trường hợp lý tưởng)
  • Tệp nguồn - thay đổi trong quá trình triển khai

Đây là một trong những thực hành. Đối với một số dự án nhỏ hơn, đó có thể là con đường để đi. Nhưng bạn có thể thử không dùng các hàm và nguyên mẫu của chúng (trong tệp tiêu đề), thay vì thay đổi chữ ký của chúng hoặc xóa chúng. Ít nhất là cho đến khi thay đổi số chính. Giống như khi 1.9.2 được chuyển sang 2.0.0 beta.
TamusJRoyce

0

Tiêu đề (.h)

  • Macro và bao gồm cần thiết cho các giao diện (càng ít càng tốt)
  • Khai báo các hàm và lớp
  • Tài liệu về giao diện
  • Khai báo các hàm / phương thức nội tuyến, nếu có
  • ngoại trừ biến toàn cục (nếu có)

Nội dung (.cpp)

  • Phần còn lại của macro và bao gồm
  • Bao gồm tiêu đề của mô-đun
  • Định nghĩa các hàm và phương pháp
  • Biến toàn cục (nếu có)

Theo quy tắc chung, bạn đặt phần "được chia sẻ" của mô-đun trên .h (phần mà các mô-đun khác cần có thể xem) và phần "không được chia sẻ" trên .cpp

PD: Vâng, tôi đã bao gồm các biến toàn cục. Tôi đã sử dụng chúng một số lần và điều quan trọng là không xác định chúng trên tiêu đề, nếu không bạn sẽ nhận được rất nhiều mô-đun, mỗi mô-đun xác định biến riêng của nó.

EDIT: Được sửa đổi sau nhận xét của David


Theo nguyên tắc chung, càng ít bao gồm càng tốt phải có trong tệp .h và tệp .cpp nên bao gồm bất kỳ tiêu đề nào mà nó cần. Điều đó rút ngắn thời gian biên dịch và không gây ô nhiễm không gian tên.
David Thornley
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.