Mengapa C ++ memiliki file header dan file .cpp?
Mengapa C ++ memiliki file header dan file .cpp?
Jawaban:
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.
Kompilasi dalam C ++ dilakukan dalam 2 fase utama:
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".
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?
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 ...
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
}
include
kerjanya?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.
}
Dalam kasus saat ini, ini tidak diperlukan, dan B.HPP memiliki doSomethingElse
deklarasi fungsi, dan B.CPP memiliki doSomethingElse
definisi 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.
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
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.
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 foo
apakah 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.
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).
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?) :)
Why can't I be sure, that foo(bar) is a function
foo 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?
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.
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.
Karena C ++ mewarisi mereka dari C. Sayangnya.
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).
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.
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:
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 ...