Mengapa memiliki file header dan file .cpp? [Tutup]

484

Mengapa C ++ memiliki file header dan file .cpp?

Peter Mortensen
sumber
3
Pertanyaan terkait: stackoverflow.com/questions/1945846/…
Spoike
itu adalah paradigma OOP yang umum, .h adalah deklarasi kelas dan cpp menjadi definisi. Seseorang tidak perlu tahu bagaimana itu diterapkan, dia hanya harus tahu antarmuka.
Manish Kakati
Ini adalah bagian terbaik dari c ++ yang memisahkan antarmuka dari implementasi. Itu selalu baik daripada menyimpan semua kode dalam satu file, kami memiliki antarmuka yang terpisah. Sejumlah kode selalu ada seperti fungsi inline yang merupakan bagian dari file header. Terlihat bagus ketika file header terlihat menampilkan daftar fungsi yang dideklarasikan dan variabel kelas.
Miank
Ada kalanya file header sangat penting untuk kompilasi - bukan hanya preferensi organisasi atau cara untuk mendistribusikan perpustakaan yang telah dikompilasi sebelumnya. Katakanlah Anda memiliki struktur di mana game.c tergantung pada KEDUA fisika.c dan matematika.c; physics.c juga tergantung pada math.c. Jika Anda memasukkan file .c dan lupa tentang file .h selamanya Anda akan memiliki deklarasi duplikat dari math.c dan tidak ada harapan kompilasi. Inilah yang paling masuk akal bagi saya mengapa file header penting. Semoga ini bisa membantu orang lain.
Samy Bencherif
Saya pikir itu ada hubungannya dengan fakta bahwa hanya karakter alfanumerik yang diizinkan dalam ekstensi. Saya bahkan tidak tahu apakah itu benar, hanya menebak
user12211554

Jawaban:

202

Nah, alasan utama adalah untuk memisahkan antarmuka dari implementasi. Header menyatakan "apa" yang akan dilakukan oleh suatu kelas (atau apa pun yang sedang dilaksanakan), sementara file cpp mendefinisikan "bagaimana" ia akan melakukan fitur-fitur tersebut.

Ini mengurangi dependensi sehingga kode yang menggunakan header tidak perlu mengetahui semua detail implementasi dan setiap kelas / header lainnya yang diperlukan hanya untuk itu. Ini akan mengurangi waktu kompilasi dan juga jumlah kompilasi yang dibutuhkan ketika sesuatu dalam implementasi berubah.

Ini tidak sempurna, dan Anda biasanya menggunakan teknik seperti Pimpl Idiom untuk memisahkan antarmuka dan implementasi dengan benar, tetapi ini adalah awal yang baik.

MadKeithV
sumber
178
Tidak sepenuhnya benar. Header masih berisi bagian utama dari implementasi. Sejak kapan variabel instance pribadi bagian dari antarmuka kelas? Fungsi anggota pribadi? Lalu apa yang mereka lakukan di header yang terlihat secara publik? Dan itu jatuh lebih jauh dengan templat.
jalf
13
Itu sebabnya saya mengatakan bahwa itu tidak sempurna, dan idiom Pimpl diperlukan untuk pemisahan lebih lanjut. Template adalah kaleng cacing yang sama sekali berbeda - bahkan jika kata kunci "ekspor" didukung penuh di sebagian besar kompiler, template saya tetap berupa gula sintaksis daripada pemisahan yang nyata.
Joris Timmermans
4
Bagaimana bahasa lain menangani ini? misalnya - Java? Tidak ada konsep file header di Jawa.
Lazer
8
@ Lazer: Java lebih mudah diurai. Kompiler Java dapat mem-parsing file tanpa mengetahui semua kelas dalam file lain, dan periksa jenisnya nanti. Dalam C ++ banyak konstruksi yang ambigu tanpa informasi jenis, sehingga kompiler C ++ membutuhkan informasi tentang tipe yang direferensikan untuk mengurai file. Itu sebabnya perlu header.
Niki
15
@nikie: Apa hubungannya dengan "kemudahan" penguraian? Jika Java memiliki tata bahasa yang setidaknya sekompleks C ++, masih bisa menggunakan file java. Dalam kedua kasus itu, bagaimana dengan C? C mudah diurai, namun menggunakan header dan file c.
Thomas Eding
609

Kompilasi C ++

Kompilasi dalam C ++ dilakukan dalam 2 fase utama:

  1. Yang pertama adalah kompilasi file teks "sumber" menjadi file "objek" biner: File CPP adalah file yang dikompilasi dan dikompilasi tanpa sepengetahuan tentang file CPP lainnya (atau bahkan perpustakaan), kecuali diumpankan melalui deklarasi mentah atau inklusi tajuk. File CPP biasanya dikompilasi menjadi file .OBJ atau .O "object".

  2. Yang kedua adalah menghubungkan bersama semua file "objek", dan dengan demikian, pembuatan file biner akhir (baik perpustakaan atau file yang dapat dieksekusi).

Di mana HPP cocok dalam semua proses ini?

File CPP kesepian yang buruk ...

Kompilasi setiap file CPP independen dari semua file CPP lainnya, yang berarti bahwa jika A.CPP memerlukan simbol yang didefinisikan dalam B.CPP, seperti:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

Itu tidak akan dikompilasi karena A.CPP tidak memiliki cara untuk mengetahui "doSomethingElse" ada ... Kecuali ada deklarasi di A.CPP, seperti:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Kemudian, jika Anda memiliki C.CPP yang menggunakan simbol yang sama, Anda kemudian menyalin / menempelkan deklarasi ...

ALERT COPY / PASTE!

Ya, ada masalah. Salinan / pasta berbahaya, dan sulit untuk dipelihara. Yang berarti itu akan keren jika kita punya cara untuk TIDAK menyalin / menempel, dan masih mendeklarasikan simbol ... Bagaimana kita bisa melakukannya? Dengan menyertakan beberapa file teks, yang biasanya diakhiri dengan .h, .hxx, .h ++ atau, saya lebih suka untuk file C ++, .hpp:

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

Bagaimana cara includekerjanya?

Termasuk sebuah file, pada dasarnya, akan menguraikan dan kemudian menyalin-menempelkan isinya dalam file CPP.

Misalnya, dalam kode berikut, dengan header A.HPP:

// A.HPP
void someFunction();
void someOtherFunction();

... sumber B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... akan menjadi setelah dimasukkan:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

Satu hal kecil - mengapa menyertakan B.HPP dalam B.CPP?

Dalam kasus saat ini, ini tidak diperlukan, dan B.HPP memiliki doSomethingElsedeklarasi fungsi, dan B.CPP memiliki doSomethingElsedefinisi fungsi (yang dengan sendirinya merupakan deklarasi). Tetapi dalam kasus yang lebih umum, di mana B.HPP digunakan untuk deklarasi (dan kode inline), mungkin tidak ada definisi yang sesuai (misalnya, enum, struct polos, dll.), Sehingga menyertakan bisa diperlukan jika B.CPP menggunakan deklarasi tersebut dari B.HPP. Semua dalam semua, itu adalah "selera yang baik" untuk suatu sumber untuk memasukkan secara default tajuknya.

Kesimpulan

Karena itu file header diperlukan, karena kompilator C ++ tidak dapat mencari deklarasi simbol saja, dan karenanya, Anda harus membantunya dengan menyertakan deklarasi tersebut.

Satu kata terakhir: Anda harus meletakkan pelindung tajuk di sekitar konten file HPP Anda, untuk memastikan beberapa inklusi tidak merusak apa pun, tetapi semuanya, saya yakin alasan utama keberadaan file HPP dijelaskan di atas.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

atau bahkan lebih sederhana

#pragma once

// The declarations in the B.hpp file
paercebal
sumber
2
@nimcap:: You still have to copy paste the signature from header file to cpp file, don't you?Tidak perlu. Selama CPP "memasukkan" HPP, precompiler akan secara otomatis melakukan copy-paste dari isi file HPP ke dalam file CPP. Saya memperbarui jawaban untuk mengklarifikasi itu.
paercebal
7
@ Bob: While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself. Tidak, tidak. Ia hanya tahu jenis yang disediakan oleh pengguna, yang akan, separuh waktu, bahkan tidak akan repot-repot membaca nilai pengembalian. Kemudian, konversi implisit terjadi. Dan kemudian, ketika Anda memiliki kode:, foo(bar)Anda bahkan tidak bisa memastikan fooapakah fungsinya. Jadi kompiler harus memiliki akses ke informasi dalam header untuk memutuskan apakah sumber mengkompilasi dengan benar, atau tidak ... Kemudian, setelah kode dikompilasi, tautan hanya akan menghubungkan fungsi panggilan.
paercebal
3
@ Bob: [terus] ... Sekarang, penghubung bisa melakukan pekerjaan yang dilakukan oleh kompiler, saya kira, yang kemudian akan membuat opsi Anda mungkin. (Saya kira ini adalah subjek dari proposisi "modul" untuk standar berikutnya). Seems, they're just a pretty ugly arbitrary design.: Jika C ++ telah dibuat pada 2012, memang. Tapi ingat C ++ dibangun di atas C pada 1980-an, dan pada saat itu, kendala sangat berbeda pada waktu itu (IIRC, diputuskan untuk tujuan adopsi untuk menjaga linker yang sama dari C).
paercebal
1
@paercebal Terima kasih atas penjelasan dan catatannya, paercebal! Mengapa saya tidak yakin, itu foo(bar)adalah fungsi - jika itu diperoleh sebagai pointer? Bahkan, berbicara tentang desain yang buruk, saya menyalahkan C, bukan C ++. Saya benar-benar tidak suka beberapa kendala dari murni C, seperti memiliki file header atau memiliki fungsi mengembalikan satu dan hanya satu nilai, sambil mengambil beberapa argumen pada input (bukankah terasa alami untuk memiliki input dan output berperilaku dengan cara yang sama ; mengapa banyak argumen, tetapi output tunggal?) :)
Boris Burkov
1
@ Bob:: Why can't I be sure, that foo(bar) is a functionfoo bisa berupa tipe, jadi Anda akan memiliki konstruktor kelas yang dipanggil. In fact, speaking of bad design, I blame C, not C++: Saya bisa menyalahkan C untuk banyak hal, tetapi dirancang pada tahun 70-an tidak akan menjadi salah satunya. Sekali lagi, kendala waktu itu ... such as having header files or having functions return one and only one value: Tuples dapat membantu mengurangi itu, serta melewati argumen dengan referensi. Sekarang, apa yang akan menjadi sintaks untuk mengambil kembali beberapa nilai, dan apakah layak untuk mengubah bahasa?
paercebal
93

Karena C, tempat konsep itu berasal, berusia 30 tahun, dan saat itu, itu adalah satu-satunya cara yang layak untuk menautkan kode bersama dari beberapa file.

Hari ini, ini adalah hack yang mengerikan yang benar-benar menghancurkan waktu kompilasi di C ++, menyebabkan banyak dependensi yang tidak perlu (karena definisi kelas dalam file header mengekspos terlalu banyak informasi tentang implementasi), dan seterusnya.

jalf
sumber
3
Saya bertanya-tanya mengapa file header (atau apa pun yang sebenarnya dibutuhkan untuk kompilasi / penautan) tidak sekadar "dihasilkan otomatis"?
Mateen Ulhaq
54

Karena di C ++, kode yang dapat dieksekusi akhir tidak membawa informasi simbol, itu kode mesin lebih atau kurang murni.

Dengan demikian, Anda memerlukan cara untuk menggambarkan antarmuka sepotong kode, yang terpisah dari kode itu sendiri. Deskripsi ini ada di file header.

beristirahat
sumber
16

Karena C ++ mewarisi mereka dari C. Sayangnya.

andref
sumber
4
Mengapa pewarisan C ++ dari C sangat disayangkan?
Lokesh
3
@Lokesh Karena barang bawaannya :(
陳 力
1
Bagaimana ini bisa menjadi jawaban?
Shuvo Sarker
14
@ ShuvoSarker karena seperti ribuan bahasa telah menunjukkan, tidak ada penjelasan teknis untuk C ++ membuat programmer menulis tanda tangan fungsi dua kali. Jawaban untuk "mengapa?" adalah "sejarah".
Boris
15

Karena orang-orang yang mendesain format perpustakaan tidak ingin "menyia-nyiakan" ruang untuk informasi yang jarang digunakan seperti makro preprocessor C dan deklarasi fungsi.

Karena Anda memerlukan info itu untuk memberi tahu kompiler Anda "fungsi ini tersedia nanti ketika linker melakukan tugasnya", mereka harus membuat file kedua di mana informasi yang dibagikan ini dapat disimpan.

Sebagian besar bahasa setelah C / C ++ menyimpan informasi ini dalam output (Java bytecode, misalnya) atau mereka tidak menggunakan format yang dikompilasi sama sekali, selalu didistribusikan dalam bentuk sumber dan kompilasi dengan cepat (Python, Perl).

Aaron Digulla
sumber
Tidak akan berhasil, referensi siklik. Saya tidak bisa membangun a.lib dari a.cpp sebelum membangun b.lib dari b.cpp, tetapi Anda juga tidak bisa membangun b.lib sebelum a.lib.
MSalters
20
Java menyelesaikannya, Python dapat melakukannya, bahasa modern apa pun dapat melakukannya. Tetapi pada saat C ditemukan, RAM sangat mahal dan langka, itu bukan pilihan.
Aaron Digulla
6

Ini adalah cara preprocessor untuk mendeklarasikan antarmuka. Anda menempatkan antarmuka (deklarasi metode) ke dalam file header, dan implementasinya ke cpp. Aplikasi yang menggunakan perpustakaan Anda hanya perlu mengetahui antarmuka, yang dapat mereka akses melalui #include.

Martin v. Löwis
sumber
4

Seringkali Anda ingin memiliki definisi antarmuka tanpa harus mengirimkan seluruh kode. Misalnya, jika Anda memiliki perpustakaan bersama, Anda akan mengirimkan file header dengan itu yang mendefinisikan semua fungsi dan simbol yang digunakan di perpustakaan bersama. Tanpa file header, Anda harus mengirimkan sumbernya.

Dalam satu proyek, file header digunakan, IMHO, untuk setidaknya dua tujuan:

  • Kejelasan, yaitu, dengan menjaga antarmuka terpisah dari implementasi, lebih mudah untuk membaca kode
  • Waktu kompilasi. Dengan hanya menggunakan antarmuka jika memungkinkan, alih-alih implementasi penuh, waktu kompilasi dapat dikurangi karena kompiler dapat dengan mudah membuat referensi ke antarmuka alih-alih harus mem-parsing kode aktual (yang, idealnya, hanya perlu dilakukan satu kali).
user21037
sumber
3
Mengapa vendor perpustakaan tidak bisa hanya mengirim file "header" yang dihasilkan? File "header" gratis pra-prosesor harus memberikan kinerja yang jauh lebih baik (kecuali implementasinya benar-benar rusak).
Tom Hawtin - tackline
Saya pikir itu tidak relevan jika file header dibuat atau ditulis tangan, pertanyaannya bukan "mengapa orang menulis file header sendiri?", Itu "mengapa kita memiliki file header". Hal yang sama berlaku untuk header bebas preprosesor. Tentu, ini akan lebih cepat.
-5

Menanggapi jawaban MadKeithV ,

Ini mengurangi dependensi sehingga kode yang menggunakan header tidak perlu mengetahui semua detail implementasi dan setiap kelas / header lainnya yang diperlukan hanya untuk itu. Ini akan mengurangi waktu kompilasi, dan juga jumlah kompilasi yang dibutuhkan ketika sesuatu dalam implementasi berubah.

Alasan lain adalah bahwa header memberikan id unik untuk setiap kelas.

Jadi jika kita punya sesuatu seperti

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

Kami akan memiliki kesalahan, ketika kami mencoba membangun proyek, karena A adalah bagian dari B, dengan header kami akan menghindari sakit kepala semacam ini ...

Alex v
sumber