Mengapa C ++ membutuhkan file header terpisah?


150

Saya tidak pernah benar-benar mengerti mengapa C ++ membutuhkan file header terpisah dengan fungsi yang sama seperti pada file .cpp. Itu membuat pembuatan kelas dan pemfaktoran ulang mereka sangat sulit, dan itu menambahkan file yang tidak perlu ke proyek. Dan kemudian ada masalah dengan harus menyertakan file header, tetapi harus secara eksplisit memeriksa apakah sudah disertakan.

C ++ diratifikasi pada tahun 1998, lalu mengapa didesain seperti ini? Apa keuntungan memiliki file header terpisah?


Pertanyaan lanjutan:

Bagaimana kompilator menemukan file .cpp dengan kode di dalamnya, jika yang saya sertakan hanyalah file .h? Apakah ini berasumsi bahwa file .cpp memiliki nama yang sama dengan file .h, atau apakah itu benar-benar melihat semua file di pohon direktori?


2
Jika ingin mengedit file tunggal hanya checkout lzz (www.lazycplusplus.com).
Richard Corden

Jawaban:


107

Anda sepertinya bertanya tentang memisahkan definisi dari deklarasi, meskipun ada kegunaan lain untuk file header.

Jawabannya adalah C ++ tidak "membutuhkan" ini. Jika Anda menandai semuanya sebaris (yang tetap otomatis untuk fungsi anggota yang ditentukan dalam definisi kelas), maka tidak perlu pemisahan. Anda bisa menentukan semuanya di file header.

Alasan Anda mungkin ingin memisahkan adalah:

  1. Untuk meningkatkan waktu pembuatan.
  2. Untuk menautkan kode tanpa sumber definisi.
  3. Untuk menghindari menandai semuanya "sebaris".

Jika pertanyaan Anda yang lebih umum adalah, "mengapa C ++ tidak identik dengan Java?", Maka saya harus bertanya, "mengapa Anda menulis C ++ daripada Java?" ;-p

Lebih serius lagi, alasannya adalah bahwa compiler C ++ tidak bisa begitu saja menjangkau unit terjemahan lain dan mencari cara menggunakan simbol-simbolnya, seperti yang bisa dan dilakukan javac. File header diperlukan untuk mendeklarasikan kepada compiler apa yang diharapkan tersedia pada waktu penautan.

Begitu #includejuga dengan substitusi tekstual lurus. Jika Anda mendefinisikan semuanya di file header, preprocessor akhirnya membuat salinan dan paste yang sangat besar dari setiap file sumber dalam proyek Anda, dan memasukkannya ke dalam kompilator. Fakta bahwa standar C ++ diratifikasi pada tahun 1998 tidak ada hubungannya dengan hal ini, itu fakta bahwa lingkungan kompilasi untuk C ++ sangat dekat dengan lingkungan C.

Mengubah komentar saya untuk menjawab pertanyaan tindak lanjut Anda:

Bagaimana kompilator menemukan file .cpp dengan kode di dalamnya

Tidak, setidaknya pada saat itu mengkompilasi kode yang menggunakan file header. Fungsi-fungsi yang Anda tautkan bahkan belum perlu ditulis, apalagi kompilator mengetahui .cppfile apa yang akan digunakan. Semua yang perlu diketahui kode pemanggil pada waktu kompilasi diekspresikan dalam deklarasi fungsi. Pada waktu tautan Anda akan memberikan daftar .ofile, atau pustaka statis atau dinamis, dan header yang berlaku adalah janji bahwa definisi fungsi akan ada di sana di suatu tempat.


3
Untuk menambahkan ke "Alasan Anda mungkin ingin memisahkan adalah:" & Hal yang paling penting dari fungsi file header adalah: Untuk Memisahkan desain struktur kode dari implementasi, Karena: A. Ketika Anda masuk ke struktur yang sangat rumit yang melibatkan banyak objek itu jauh lebih mudah untuk menyaring file header dan mengingat bagaimana mereka bekerja bersama, dilengkapi dengan komentar header Anda. B. Jika satu orang tidak mengurus pendefinisian semua struktur objek dan beberapa yang lain mengurus implementasi, hal itu membuat semuanya tetap teratur. Secara keseluruhan, saya pikir itu membuat kode kompleks lebih mudah dibaca.
Andres Canella

Dalam cara yang paling sederhana saya dapat memikirkan kegunaan pemisahan file header vs cpp adalah untuk memisahkan Antarmuka vs Implementasi yang benar-benar membantu untuk proyek menengah / besar.
Krishna Oza

Saya bermaksud agar dimasukkan dalam (2), "tautan terhadap kode tanpa definisi". Oke, jadi Anda mungkin masih memiliki akses ke repo git dengan definisi di, tetapi intinya adalah Anda dapat mengompilasi kode Anda secara terpisah dari implementasi dependensinya dan kemudian menautkan. Jika secara harfiah semua yang Anda inginkan adalah memisahkan antarmuka / implementasi ke dalam file yang berbeda, tanpa mempedulikan untuk membangun secara terpisah, maka Anda dapat melakukannya hanya dengan memiliki foo_interface.h termasuk foo_implementation.h.
Steve Jessop

6
@AndresCanella Tidak, tidak. Itu membuat membaca dan memelihara bukan-kode-Anda-sendiri menjadi mimpi buruk. Untuk sepenuhnya memahami apa yang dilakukan sesuatu dalam kode, Anda perlu melompati file 2n daripada n file. Ini bukan notasi Big-Oh, 2n membuat banyak perbedaan dibandingkan dengan hanya n.
Błażej Michalik

1
Saya setuju bahwa itu adalah kebohongan yang membantu header. periksa sumber minix misalnya, sangat sulit untuk mengikuti dari mana ia mulai ke mana kontrol dilewatkan, di mana hal-hal dideklarasikan / ditentukan .. jika itu dibangun melalui modul dinamis yang terpisah, itu akan dapat dicerna dengan memahami satu hal lalu melompat ke modul ketergantungan. sebagai gantinya, Anda harus mengikuti header dan itu membuat membaca kode apa pun yang ditulis dengan cara ini seperti neraka. sebaliknya, nodejs memperjelas dari mana asalnya tanpa ifdef, dan Anda dapat dengan mudah mengidentifikasi dari mana asalnya.
Dmitry

93

C ++ melakukannya seperti itu karena C melakukannya seperti itu, jadi pertanyaan sebenarnya adalah mengapa C melakukannya seperti itu? Wikipedia berbicara sedikit tentang ini.

Bahasa terkompilasi yang lebih baru (seperti Java, C #) tidak menggunakan deklarasi maju; pengenal dikenali secara otomatis dari file sumber dan dibaca langsung dari simbol perpustakaan dinamis. Ini berarti file header tidak diperlukan.


13
+1 Memukul paku di kepala. Ini benar-benar tidak membutuhkan penjelasan yang panjang lebar.
MSalters

6
Itu tidak menyentuh kepala saya :( Saya masih harus mencari tahu mengapa C ++ harus menggunakan deklarasi maju dan mengapa ia tidak dapat mengenali pengidentifikasi dari file sumber dan membaca langsung dari simbol perpustakaan dinamis, dan mengapa C ++ melakukannya seperti itu seperti itu hanya karena C melakukannya seperti itu: p
Alexander Taylor

3
Dan Anda adalah programmer yang lebih baik karena telah melakukannya @AlexanderTaylor :)
Donald Byrd

74

Beberapa orang menganggap file header sebagai keuntungan:

  • Dikatakan bahwa itu memungkinkan / memberlakukan / memungkinkan pemisahan antarmuka dan implementasi - tetapi biasanya, ini tidak terjadi. File header penuh dengan detail implementasi (misalnya variabel anggota kelas harus ditentukan di header, meskipun mereka bukan bagian dari antarmuka publik), dan fungsi dapat, dan sering, didefinisikan sebaris dalam deklarasi kelas di header, sekali lagi menghancurkan pemisahan ini.
  • Kadang-kadang dikatakan untuk meningkatkan waktu kompilasi karena setiap unit terjemahan dapat diproses secara mandiri. Namun C ++ mungkin adalah bahasa paling lambat yang pernah ada dalam hal waktu kompilasi. Salah satu alasannya adalah banyaknya inklusi berulang dari header yang sama. Sejumlah besar header disertakan oleh beberapa unit terjemahan, yang mengharuskannya diurai beberapa kali.

Pada akhirnya, sistem header adalah artefak dari tahun 70-an ketika C dirancang. Saat itu, komputer memiliki sedikit memori, dan menyimpan seluruh modul dalam memori bukanlah pilihan. Kompiler harus mulai membaca file dari atas, dan kemudian melanjutkan secara linier melalui kode sumber. Mekanisme header memungkinkan ini. Kompilator tidak harus mempertimbangkan unit terjemahan lain, ia hanya perlu membaca kode dari atas ke bawah.

Dan C ++ mempertahankan sistem ini untuk kompatibilitas ke belakang.

Hari ini, itu tidak masuk akal. Itu tidak efisien, rawan kesalahan dan terlalu rumit. Ada cara yang jauh lebih baik untuk memisahkan antarmuka dan implementasi, jika itu adalah tujuan.

Namun, salah satu proposal untuk C ++ 0x adalah untuk menambahkan sistem modul yang tepat, yang memungkinkan kode untuk dikompilasi mirip dengan .NET atau Java, menjadi modul yang lebih besar, semuanya dalam sekali jalan dan tanpa header. Proposal ini tidak lolos di C ++ 0x, tapi saya yakin itu masih dalam kategori "kami akan senang melakukannya nanti". Mungkin di TR2 atau serupa.


2
INI adalah jawaban terbaik di halaman ini. Terima kasih!
Chuck Le Butt

1
Jawaban ini harus menjadi salah satu yang diterima karena itu benar-benar menjelaskan mengapa C ++ dirancang seperti itu, dan bukan "mengapa Anda mungkin ingin memisahkan"
SubMachine

30

Untuk pemahaman saya (terbatas - saya bukan C developer biasanya), ini berakar pada C. Ingat bahwa C tidak tahu apa itu kelas atau namespace, itu hanya satu program yang panjang. Selain itu, fungsi harus dideklarasikan sebelum Anda menggunakannya.

Misalnya, berikut ini akan memberikan kesalahan kompiler:

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Kesalahannya adalah "SomeOtherFunction tidak dideklarasikan" karena Anda memanggilnya sebelum deklarasinya. Salah satu cara untuk memperbaikinya adalah dengan memindahkan SomeOtherFunction di atas SomeFunction. Pendekatan lain adalah mendeklarasikan tanda tangan fungsi terlebih dahulu:

void SomeOtherFunction();

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

Hal ini memungkinkan kompilator mengetahui: Lihat di suatu tempat di kode, ada fungsi bernama SomeOtherFunction yang mengembalikan void dan tidak mengambil parameter apa pun. Jadi jika Anda encouter kode yang mencoba memanggil SomeOtherFunction, jangan panik dan cari saja.

Sekarang, bayangkan Anda memiliki SomeFunction dan SomeOtherFunction dalam dua file .c yang berbeda. Anda kemudian harus #memasukkan "SomeOther.c" di Some.c. Sekarang, tambahkan beberapa fungsi "pribadi" ke SomeOther.c. Karena C tidak mengetahui fungsi privat, fungsi itu akan tersedia di Some.c juga.

Di sinilah File .h masuk: Mereka menentukan semua fungsi (dan variabel) yang ingin Anda 'Ekspor' dari file .c yang dapat diakses di file .c lainnya. Dengan begitu, Anda mendapatkan sesuatu seperti cakupan Publik / Pribadi. Selain itu, Anda dapat memberikan file .h ini kepada orang lain tanpa harus membagikan kode sumber Anda - file .h juga dapat digunakan pada file .lib yang dikompilasi.

Jadi, alasan utamanya adalah untuk kenyamanan, untuk perlindungan kode sumber dan untuk memiliki sedikit pemisahan antara bagian-bagian aplikasi Anda.

Itu C sekalipun. C ++ memperkenalkan Classes dan pengubah privat / publik, jadi meskipun Anda masih bisa menanyakan apakah mereka diperlukan, C ++ AFAIK masih memerlukan deklarasi fungsi sebelum menggunakannya. Juga, banyak Pengembang C ++ adalah atau merupakan pengembang C juga dan mengambil alih konsep dan kebiasaan mereka ke C ++ - mengapa mengubah apa yang tidak rusak?


5
Mengapa kompilator tidak dapat menjalankan kode dan menemukan semua definisi fungsi? Sepertinya sesuatu yang cukup mudah untuk diprogram ke dalam kompiler.
Marius

3
Jika Anda memiliki sumbernya, yang seringkali tidak Anda miliki. C ++ yang dikompilasi adalah kode mesin yang efektif dengan informasi tambahan yang cukup untuk memuat dan menautkan kode. Kemudian, Anda arahkan CPU ke titik masuk, dan biarkan berjalan. Ini pada dasarnya berbeda dari Java atau C #, di mana kode dikompilasi menjadi bytecode perantara yang berisi metadata pada isinya.
DevSolar

3
Yup - menyusun 16 pahit dengan tape mass torage bukanlah hal yang sepele.
MSalters

1
@Puddle Saya rasa itu bukan alasan sebenarnya, karena di tahun 70-an ketika C dikembangkan, berbagi kode sumber adalah norma daripada pengecualian. Saya percaya itu karena akses acak ke file tidak mungkin dilakukan dengan mudah - saat itu, menggunakan pita magnetik adalah hal yang umum, dan bahasa dapat dikompilasi hanya dengan maju melalui file, tidak pernah mundur atau melompat-lompat. File .h tampak seperti cara yang bagus untuk memajukan deklarasi tanpa menimbulkan kekacauan implementasi yang lebih besar.
Michael Stum

1
@MichaelStum tapi kenapa? mengapa mereka menyimpannya? bahasa adalah tentang memahami tujuan dari apa yang ditulis oleh pemrogram. semua orang dapat memahami cara membuat header berdasarkan semua kelas. itu adalah tugas yang tidak berarti jika secara harfiah tidak melakukan apa-apa selain membantu kompilasi c ++. kami telah pindah dan dapat membuatnya otomatis jika tidak ada yang lain. jika tidak ada tujuan lain ...
Genangan

12

Keuntungan pertama: Jika Anda tidak memiliki file header, Anda harus menyertakan file sumber di file sumber lain. Ini akan menyebabkan file yang termasuk dikompilasi lagi ketika file yang disertakan berubah.

Keuntungan kedua: Ini memungkinkan berbagi antarmuka tanpa berbagi kode antara unit yang berbeda (pengembang, tim, perusahaan yang berbeda, dll ..)


1
Apakah Anda menyiratkan bahwa, misalnya dalam C # 'Anda harus menyertakan file sumber dalam file sumber lain'? Karena jelas tidak. Untuk keuntungan kedua, saya pikir itu terlalu bergantung pada bahasa: Anda tidak akan menggunakan file .h misalnya Delphi
Vlagged

Anda harus mengkompilasi ulang seluruh proyek, jadi apakah keuntungan pertama benar-benar dihitung?
Marius

ok, tapi menurut saya itu bukan fitur bahasa. Lebih praktis menangani deklarasi C sebelum mendefinisikan "masalah". Ini seperti seseorang yang terkenal mengatakan "itu bukan bug yang merupakan fitur" :)
neuro

@Marius: Ya, itu benar-benar penting. Menautkan keseluruhan proyek berbeda dengan menyusun & menautkan keseluruhan proyek. Dan ketika # file dalam proyek meningkat, mengkompilasi semuanya menjadi sangat mengganggu. @Vlagged: Anda benar, tetapi saya tidak membandingkan c ++ dengan bahasa lain. Saya membandingkan hanya menggunakan file sumber vs menggunakan file sumber & header.
erelender

C # tidak menyertakan file sumber pada orang lain, tetapi Anda masih harus mereferensikan modul - dan itu membuat compiler mengambil file sumber (atau mencerminkan ke dalam biner) untuk mengurai simbol yang digunakan kode Anda.
gbjbaanb

6

Kebutuhan akan file header diakibatkan oleh keterbatasan yang dimiliki compiler untuk mengetahui informasi jenis untuk fungsi dan atau variabel dalam modul lain. Program atau pustaka yang dikompilasi tidak menyertakan informasi jenis yang diperlukan oleh kompilator untuk mengikat objek apa pun yang ditentukan dalam unit kompilasi lain.

Untuk mengimbangi batasan ini, C dan C ++ memungkinkan deklarasi dan deklarasi ini dapat dimasukkan ke dalam modul yang menggunakannya dengan bantuan arahan #include preprocessor.

Bahasa seperti Java atau C # di sisi lain menyertakan informasi yang diperlukan untuk mengikat dalam keluaran kompiler (file kelas atau perakitan). Oleh karena itu, tidak perlu lagi mempertahankan deklarasi mandiri untuk disertakan oleh klien modul.

Alasan informasi binding tidak disertakan dalam output compiler adalah sederhana: tidak diperlukan pada waktu proses (semua jenis pemeriksaan terjadi pada waktu kompilasi). Itu hanya akan membuang-buang ruang. Ingatlah bahwa C / C ++ berasal dari waktu di mana ukuran executable atau pustaka cukup penting.


Saya setuju dengan kamu. Saya mendapat ide serupa di sini: stackoverflow.com/questions/3702132/…
smwikipedia

4

C ++ dirancang untuk menambahkan fitur bahasa pemrograman modern ke infrastruktur C, tanpa perlu mengubah apa pun tentang C yang tidak secara khusus tentang bahasa itu sendiri.

Ya, pada titik ini (10 tahun setelah standar C ++ pertama dan 20 tahun setelah standar tersebut mulai berkembang secara serius dalam penggunaan) mudah untuk ditanyakan mengapa tidak memiliki sistem modul yang tepat. Jelas sekali bahasa baru apa pun yang sedang dirancang saat ini tidak akan berfungsi seperti C ++. Tapi itu bukan inti dari C ++.

Inti dari C ++ adalah untuk menjadi evolusioner, kelanjutan yang mulus dari praktik yang ada, hanya menambahkan kemampuan baru tanpa (terlalu sering) merusak hal-hal yang berfungsi secara memadai untuk komunitas penggunanya.

Ini berarti itu membuat beberapa hal menjadi lebih sulit (terutama bagi orang yang memulai proyek baru), dan beberapa hal lebih mudah (terutama bagi mereka yang memelihara kode yang ada) daripada yang dilakukan bahasa lain.

Jadi daripada mengharapkan C ++ berubah menjadi C # (yang tidak ada gunanya karena kita sudah memiliki C #), mengapa tidak memilih alat yang tepat untuk pekerjaan itu? Saya sendiri, saya berusaha untuk menulis potongan signifikan dari fungsionalitas baru dalam bahasa modern (saya kebetulan menggunakan C #), dan saya memiliki sejumlah besar C ++ yang saya simpan di C ++ karena tidak akan ada nilai nyata dalam menulis ulang itu semua. Mereka berintegrasi dengan sangat baik, jadi sebagian besar tidak menimbulkan rasa sakit.


Bagaimana Anda mengintegrasikan C # dan C ++? Melalui COM?
Peter Mortensen

1
Ada tiga cara utama, yang "terbaik" bergantung pada kode yang ada. Saya telah menggunakan ketiganya. Yang paling saya gunakan adalah COM karena kode saya yang ada sudah dirancang di sekitarnya, jadi praktis tanpa hambatan, bekerja dengan sangat baik untuk saya. Di beberapa tempat aneh saya menggunakan C ++ / CLI yang memberikan integrasi yang sangat mulus untuk situasi apa pun di mana Anda belum memiliki antarmuka COM (dan Anda mungkin lebih suka menggunakan antarmuka COM yang ada bahkan jika Anda memilikinya). Akhirnya ada p / pemanggilan yang pada dasarnya memungkinkan Anda memanggil fungsi mirip-C yang diekspos dari DLL, jadi memungkinkan Anda langsung memanggil API Win32 dari C #.
Daniel Earwicker

4

Nah, C ++ telah diratifikasi pada tahun 1998, tetapi telah digunakan lebih lama dari itu, dan ratifikasi tersebut terutama menetapkan penggunaan saat ini daripada memaksakan struktur. Dan karena C ++ didasarkan pada C, dan C memiliki file header, C ++ juga memilikinya.

Alasan utama untuk file header adalah untuk mengaktifkan kompilasi file terpisah, dan meminimalkan ketergantungan.

Katakanlah saya memiliki foo.cpp, dan saya ingin menggunakan kode dari file bar.h / bar.cpp.

Saya bisa # menyertakan "bar.h" di foo.cpp, dan kemudian memprogram dan mengkompilasi foo.cpp bahkan jika bar.cpp tidak ada. File header bertindak sebagai janji kepada compiler bahwa class / functions di bar.h akan ada pada saat run-time, dan memiliki semua yang perlu diketahui.

Tentu saja, jika fungsi di bar.h tidak memiliki badan ketika saya mencoba menautkan program saya, maka itu tidak akan menautkan dan saya akan mendapatkan kesalahan.

Efek sampingnya adalah Anda dapat memberikan file header kepada pengguna tanpa mengungkapkan kode sumber Anda.

Hal lainnya adalah jika Anda mengubah implementasi kode Anda di file * .cpp, tetapi tidak mengubah header sama sekali, Anda hanya perlu mengompilasi file * .cpp daripada semua yang menggunakannya. Tentu saja, jika Anda menerapkan banyak implementasi ke file header, maka ini menjadi kurang berguna.


3

Itu tidak membutuhkan file header terpisah dengan fungsi yang sama seperti di main. Ini hanya membutuhkannya jika Anda mengembangkan aplikasi menggunakan beberapa file kode dan jika Anda menggunakan fungsi yang tidak dideklarasikan sebelumnya.

Ini benar-benar masalah ruang lingkup.


1

C ++ diratifikasi pada tahun 1998, lalu mengapa didesain seperti ini? Apa keuntungan memiliki file header terpisah?

Sebenarnya file header menjadi sangat berguna saat memeriksa program untuk pertama kalinya, memeriksa file header (hanya menggunakan editor teks) memberi Anda gambaran umum tentang arsitektur program, tidak seperti bahasa lain di mana Anda harus menggunakan alat canggih untuk melihat kelas dan fungsi anggota mereka.


1

Saya pikir alasan sebenarnya (historis) di balik file header membuat lebih mudah bagi pengembang kompiler ... tetapi kemudian, file header memang memberikan keuntungan.
Periksa posting sebelumnya ini untuk diskusi lebih lanjut ...


1

Nah, Anda dapat mengembangkan C ++ dengan sempurna tanpa file header. Faktanya, beberapa pustaka yang secara intensif menggunakan templat tidak menggunakan paradigma file header / kode (lihat peningkatan). Tetapi Di C / C ++ Anda tidak dapat menggunakan sesuatu yang tidak dideklarasikan. Salah satu cara praktis untuk mengatasinya adalah dengan menggunakan file header. Plus, Anda mendapatkan keuntungan dari berbagi antarmuka tanpa kode / implementasi berbagi. Dan saya pikir itu tidak dibayangkan oleh pembuat C: Saat Anda menggunakan file header bersama, Anda harus menggunakan yang terkenal:

#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN

// [...]
// my header
// [...]

#endif // MY_HEADER_SWEET_GUARDIAN

itu sebenarnya bukan fitur bahasa tetapi cara praktis untuk menangani banyak penyertaan.

Jadi, saya pikir ketika C dibuat, masalah dengan deklarasi maju diremehkan dan sekarang ketika menggunakan bahasa tingkat tinggi seperti C ++ kita harus berurusan dengan hal-hal semacam ini.

Beban lain bagi kami pengguna C ++ yang malang ...


1

Jika Anda ingin kompilator menemukan simbol yang didefinisikan dalam file lain secara otomatis, Anda perlu memaksa pemrogram untuk meletakkan file-file tersebut di lokasi yang telah ditentukan (seperti struktur paket Java menentukan struktur folder proyek). Saya lebih suka file header. Anda juga memerlukan sumber pustaka yang Anda gunakan atau beberapa cara seragam untuk meletakkan informasi yang dibutuhkan oleh kompilator dalam binari.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.