Apa yang harus dimasukkan ke dalam file .h?

93

Saat membagi kode Anda menjadi beberapa file, apa sebenarnya yang harus dimasukkan ke dalam file .h dan apa yang harus dimasukkan ke file .cpp?

Enrico Tuvera Jr
sumber
Pertanyaan terkait: stackoverflow.com/questions/333889/…
Spoike
7
Ini adalah masalah gaya murni, tetapi saya percaya bahwa deklarasi C ++ masuk ke dalam .hppfile sementara deklarasi C masuk ke dalam .hfile. Ini sangat membantu saat mencampur kode C dan C ++ (misalnya modul lama di C).
Thomas Matthews
@ThomasMasuk Masuk akal. Apakah praktik itu sering digunakan?
ty
@lightningleaf: Ya, praktik ini sering digunakan terutama saat mencampur bahasa C ++ dan C.
Thomas Matthews

Jawaban:

113

File header ( .h) dirancang untuk memberikan informasi yang akan dibutuhkan dalam banyak file. Hal-hal seperti deklarasi kelas, prototipe fungsi, dan enumerasi biasanya masuk dalam file header. Singkatnya, "definisi".

File kode ( .cpp) dirancang untuk memberikan informasi implementasi yang hanya perlu diketahui dalam satu file. Secara umum, badan fungsi, dan variabel internal yang seharusnya / tidak akan pernah diakses oleh modul lain, adalah milik dalam .cppfile. Singkatnya, "implementasi".

Pertanyaan paling sederhana untuk ditanyakan pada diri Anda sendiri untuk menentukan milik mana adalah "jika saya mengubah ini, apakah saya harus mengubah kode di file lain untuk membuat semuanya terkompilasi lagi?" Jika jawabannya adalah "ya", kemungkinan itu ada di file header; jika jawabannya "tidak", kemungkinan itu termasuk dalam file kode.

Amber
sumber
4
Kecuali data kelas privat harus masuk ke header. Template harus benar-benar ditentukan header (kecuali Anda menggunakan salah satu dari sedikit kompiler yang mendukung export). Satu-satunya cara # 1 adalah PIMPL. # 2 akan dimungkinkan jika exportdidukung dan dimungkinkan menggunakan c ++ 0x dan externtemplate. IMO, file header di c ++ kehilangan banyak kegunaannya.
KitsuneYMG
23
Semuanya bagus, tapi dengan terminologi yang tidak akurat. Singkatnya, "deklarasi" - istilah "definisi" sama dengan "implementasi". Hanya kode deklaratif, kode inline, definisi makro, dan kode template yang harus ada di header; yaitu tidak ada yang memberi contoh kode atau data.
Clifford
8
Saya harus setuju dengan Clifford. Anda menggunakan istilah deklarasi dan definisi secara longgar dan saling bergantian. Tetapi mereka memiliki arti yang tepat dalam C ++. Contoh: Deklarasi kelas memperkenalkan nama kelas tetapi tidak mengatakan apa yang ada di dalamnya. Definisi kelas mencantumkan semua fungsi anggota dan teman. Keduanya dapat dimasukkan ke dalam file header tanpa masalah. Apa yang Anda sebut "prototipe fungsi" adalah deklarasi fungsi . Tetapi definisi fungsi adalah hal yang berisi kode fungsi dan harus ditempatkan ke dalam file cpp - kecuali itu inline atau (bagian dari) template.
sellibitze
5
Mereka memiliki arti yang tepat dalam C ++, mereka tidak memiliki arti yang tepat dalam bahasa Inggris. Jawaban saya ditulis di yang terakhir.
Amber
54

Faktanya, dalam C ++, ini agak lebih rumit daripada organisasi header / sumber C.

Apa yang dilihat kompilator?

Kompilator melihat satu file sumber besar (.cpp) dengan header yang disertakan dengan benar. File sumber adalah unit kompilasi yang akan dikompilasi menjadi file objek.

Jadi, mengapa header diperlukan?

Karena satu unit kompilasi mungkin memerlukan informasi tentang implementasi di unit kompilasi lain. Jadi seseorang dapat menulis misalnya implementasi suatu fungsi di satu sumber, dan menulis deklarasi fungsi ini di sumber lain yang perlu menggunakannya.

Dalam kasus ini, ada dua salinan dari informasi yang sama. Yang mana yang jahat ...

Solusinya adalah membagikan beberapa detail. Meskipun implementasinya harus tetap di Source, deklarasi simbol bersama, seperti fungsi, atau definisi struktur, kelas, enum, dll., Mungkin perlu dibagikan.

Header digunakan untuk meletakkan detail yang dibagikan tersebut.

Pindah ke tajuk deklarasi tentang apa yang perlu dibagikan antara berbagai sumber

Tidak ada lagi?

Di C ++, ada beberapa hal lain yang dapat diletakkan di header karena, mereka juga perlu dibagikan:

  • kode sebaris
  • template
  • konstanta (biasanya yang ingin Anda gunakan di dalam sakelar ...)

Pindah ke tajuk SEMUANYA yang perlu dibagikan, termasuk penerapan bersama

Apakah itu berarti bahwa mungkin ada sumber di dalam tajuk?

Iya. Faktanya, ada banyak hal berbeda yang mungkin ada di dalam "header" (yaitu, dibagikan antar sumber).

  • Deklarasi maju
  • deklarasi / definisi fungsi / structs / class / template
  • implementasi kode inline dan templated

Ini menjadi rumit, dan dalam beberapa kasus (ketergantungan melingkar antar simbol), tidak mungkin untuk menyimpannya dalam satu tajuk.

Header dapat dibagi menjadi tiga bagian

Artinya, dalam kasus ekstrem, Anda dapat memiliki:

  • header deklarasi ke depan
  • header deklarasi / definisi
  • sebuah header implementasi
  • sumber implementasi

Mari kita bayangkan kita memiliki MyObject template. Kami dapat memiliki:

// - - - - 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.

Wow!

Dalam "kehidupan nyata", biasanya tidak terlalu rumit. Kebanyakan kode hanya akan memiliki tajuk / organisasi sumber sederhana, dengan beberapa kode sebaris di sumbernya.

Tetapi dalam kasus lain (objek templated mengetahui satu sama lain), saya harus memiliki untuk setiap objek deklarasi terpisah dan header implementasi, dengan sumber kosong termasuk header tersebut hanya untuk membantu saya melihat beberapa kesalahan kompilasi.

Alasan lain untuk memecah tajuk menjadi tajuk yang terpisah bisa jadi untuk mempercepat kompilasi, membatasi jumlah simbol yang diuraikan hingga batas yang diperlukan, dan menghindari kompilasi ulang yang tidak perlu dari sumber yang hanya peduli pada deklarasi maju ketika penerapan metode sebaris berubah.

Kesimpulan

Anda harus membuat organisasi kode Anda sesederhana mungkin, dan sesederhana mungkin. Letakkan sebanyak mungkin di file sumber. Hanya tampilkan di header apa yang perlu dibagikan.

Tetapi jika Anda memiliki ketergantungan melingkar antara objek template, jangan heran jika organisasi kode Anda menjadi lebih "menarik" daripada organisasi header / sumber biasa ...

^ _ ^

paercebal.dll
sumber
17

Selain semua jawaban lainnya, saya akan memberi tahu Anda apa yang TIDAK Anda tempatkan di file header:
usingdeklarasi (yang paling umum using namespace std;) tidak boleh muncul di file header karena mereka mencemari namespace dari file sumber yang disertakan .

Adrien Plisson
sumber
+1 dengan peringatan yang dapat Anda lakukan selama menggunakan namespace detail (atau namespace anonim). Tapi ya, jangan pernah gunakan usinguntuk membawa barang ke namespace global di header.
KitsuneYMG
+1 Yang ini lebih mudah dijawab. :) Selain itu, file header tidak boleh berisi namespace anonim .
sellibitze
Tidak masalah jika file header berisi namespace anonim, selama Anda memahami artinya, yaitu setiap unit terjemahan akan memiliki salinan berbeda dari hal-hal yang Anda tentukan namespace. Fungsi sebaris di namespace anonim direkomendasikan di C ++ untuk kasus yang Anda gunakan static inlinedi C99, karena ada hubungannya dengan apa yang terjadi saat Anda menggabungkan tautan internal dengan template. Ruang nama Anon memungkinkan Anda "menyembunyikan" fungsi, sambil mempertahankan tautan eksternal.
Steve Jessop
Steve, apa yang Anda tulis tidak meyakinkan saya. Pilih contoh konkret di mana menurut Anda namespace anon benar-benar masuk akal dalam file header.
sellibitze
6

Apa yang dikompilasi menjadi tidak ada (jejak biner nol) masuk ke file header.

Variabel tidak dikompilasi menjadi apa-apa, tetapi deklarasi tipe melakukan (karena mereka hanya menjelaskan bagaimana variabel berperilaku).

fungsi tidak, tetapi fungsi sebaris melakukannya (atau makro), karena mereka menghasilkan kode hanya jika dipanggil.

template bukanlah kode, mereka hanya resep untuk membuat kode. jadi mereka juga masuk ke file h.

Pavel Radzivilovsky
sumber
1
"fungsi inline ... menghasilkan kode hanya jika dipanggil". Itu tidak benar. fungsi sebaris mungkin atau mungkin tidak sebaris di situs panggilan, tetapi meskipun mereka sebaris, badan fungsi sebenarnya masih ada seperti halnya fungsi non-sebaris. Alasan tidak mengapa memiliki fungsi inline di header tidak ada hubungannya dengan apakah mereka menghasilkan kode, itu karena fungsi inline tidak memicu aturan satu definisi, jadi tidak seperti fungsi non-inline, tidak ada masalah dengan menghubungkan dua unit terjemahan yang berbeda. yang keduanya menyertakan tajuk.
Steve Jessop
3

Secara umum, Anda meletakkan deklarasi di file header dan definisi di file implementasi (.cpp). Pengecualian untuk ini adalah template, di mana definisi juga harus ditempatkan di header.

Pertanyaan ini dan yang serupa dengan itu telah sering ditanyakan di SO - lihat Mengapa file header dan file .cpp di C ++? dan File Header C ++, Pemisahan Kode misalnya.

Komunitas
sumber
tentu saja, Anda juga dapat memasukkan definisi kelas ke dalam file header. Mereka bahkan tidak harus berupa template.
sellibitze
1

Deklarasi kelas dan fungsi Anda ditambah dengan dokumentasi, dan definisi untuk fungsi / metode inline (meskipun beberapa lebih suka meletakkannya dalam file .inl terpisah).

Alexander Gessler
sumber
1

Terutama file header berisi kerangka kelas atau deklarasi (tidak sering berubah)

dan file cpp berisi implementasi kelas (sering berubah).

Ashish
sumber
5
Harap jangan menggunakan terminologi non-standar. Apa itu "kerangka kelas", apa itu "implementasi kelas"? Juga, apa yang Anda sebut deklarasi dalam konteks kelas mungkin menyertakan definisi kelas.
sellibitze
0

file header (.h) harus untuk deklarasi kelas, struct dan metodenya, prototipe, dll. Implementasi objek tersebut dibuat di cpp.

dalam .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}
jose
sumber
0

Saya berharap untuk melihat:

  • deklarasi
  • komentar
  • definisi ditandai sebaris
  • template

jawaban sebenarnya adalah apa yang tidak harus dimasukkan:

  • definisi (dapat menyebabkan hal-hal menjadi lebih banyak didefinisikan)
  • menggunakan deklarasi / arahan (memaksa mereka pada siapa pun termasuk header Anda, dapat menyebabkan nameclash)
jk.
sumber
1
Anda juga dapat memasukkan definisi kelas ke dalam file header. Sebuah deklarasi kelas tidak mengatakan apa-apa tentang anggotanya.
sellibitze
0

Header Mendefinisikan sesuatu tapi tidak menceritakan apapun tentang implementasinya. (Tidak termasuk Template di "metafore" ini.

Dengan demikian, Anda perlu membagi "definisi" ke dalam sub-kelompok, dalam hal ini, dua jenis definisi.

  • Anda menentukan "tata letak" dari strucutre Anda, memberi tahu hanya sebanyak yang diperlukan oleh grup penggunaan di sekitarnya.
  • Definisi variabel, fungsi dan kelas.

Sekarang, saya tentu saja berbicara tentang subkelompok pertama.

Header ada di sana untuk menentukan tata letak struktur Anda untuk membantu perangkat lunak lainnya menggunakan implementasi. Anda mungkin ingin melihatnya sebagai "abstraksi" dari implementasi Anda, yang dengan jelas dikatakan tetapi, menurut saya itu cukup cocok untuk kasus ini.

Seperti yang dikatakan dan ditunjukkan poster sebelumnya, Anda mendeklarasikan area penggunaan pribadi dan publik dan headernya, ini juga termasuk variabel privat dan publik. Sekarang, saya tidak ingin membahas desain kode di sini, tetapi Anda mungkin ingin mempertimbangkan apa yang Anda masukkan ke header, karena itu adalah Layer antara pengguna akhir dan implementasinya.

Filip Ekberg
sumber
0
  • File header - tidak boleh berubah selama pengembangan terlalu sering -> Anda harus berpikir, dan menulisnya sekaligus (dalam kasus yang ideal)
  • File sumber - perubahan selama implementasi
nothrow
sumber
Ini adalah satu praktik. Untuk beberapa proyek yang lebih kecil, mungkin itu cara yang tepat. Tapi Anda mungkin mencoba menghentikan fungsi dan prototipe mereka (dalam file header), daripada mengubah tanda tangan atau menghapusnya. Setidaknya sampai mengganti nomor utama. Seperti saat 1.9.2 diubah menjadi 2.0.0 beta.
TamusJRoyce
0

Tajuk (.h)

  • Makro dan termasuk yang diperlukan untuk antarmuka (sesedikit mungkin)
  • Deklarasi fungsi dan kelas
  • Dokumentasi antarmuka
  • Deklarasi fungsi / metode sebaris, jika ada
  • eksternal ke variabel global (jika ada)

Tubuh (.cpp)

  • Makro lainnya dan termasuk
  • Sertakan tajuk modul
  • Pengertian fungsi dan metode
  • Variabel global (jika ada)

Sebagai aturan praktis, Anda meletakkan bagian "bersama" dari modul di .h (bagian yang perlu dilihat modul lain) dan bagian "tidak dibagikan" di .cpp

PD: Ya, saya sudah memasukkan variabel global. Saya telah menggunakannya beberapa kali dan penting untuk tidak mendefinisikannya di header, atau Anda akan mendapatkan banyak modul, masing-masing mendefinisikan variabelnya sendiri.

EDIT: Diubah setelah komentar David

Khelben
sumber
Sebagai aturan praktis, sesedikit mungkin penyertaan harus ada dalam file .h, dan file .cpp harus menyertakan header apa pun yang diperlukan. Itu mempersingkat waktu kompilasi dan tidak mencemari namespace.
David Thornley