Mengapa kita perlu menyertakan file .h
dan .cpp
sekaligus sementara kita dapat membuatnya bekerja hanya dengan memasukkan .cpp
file?
Misalnya: membuat file.h
deklarasi yang mengandung, lalu membuat file.cpp
definisi yang mengandung dan memasukkan keduanya dalam main.cpp
.
Atau: membuat file.cpp
deklarasi / definisi yang mengandung (tidak ada prototipe) termasuk di dalamnya main.cpp
.
Keduanya bekerja untuk saya. Saya tidak bisa melihat perbedaannya. Mungkin beberapa wawasan tentang proses kompilasi dan penautan dapat membantu.
Jawaban:
Meskipun Anda dapat memasukkan
.cpp
file seperti yang Anda sebutkan, ini adalah ide yang buruk.Seperti yang Anda sebutkan, deklarasi termasuk dalam file header. Ini tidak menyebabkan masalah ketika dimasukkan dalam beberapa unit kompilasi karena mereka tidak menyertakan implementasi. Menyertakan definisi fungsi atau anggota kelas beberapa kali biasanya akan menyebabkan masalah (tetapi tidak selalu) karena linker akan menjadi bingung dan melempar kesalahan.
Apa yang harus terjadi adalah setiap
.cpp
file menyertakan definisi untuk subset dari program, seperti kelas, kelompok fungsi yang diatur secara logis, variabel statis global (gunakan hemat jika sama sekali), dll.Setiap unit kompilasi (
.cpp
file) kemudian menyertakan deklarasi apa pun yang diperlukan untuk mengkompilasi definisi yang dikandungnya. Itu melacak fungsi dan kelas referensi tetapi tidak mengandung, sehingga linker dapat menyelesaikannya nanti ketika menggabungkan kode objek ke dalam executable atau library.Contoh
Foo.h
-> mengandung deklarasi (antarmuka) untuk kelas Foo.Foo.cpp
-> mengandung definisi (implementasi) untuk kelas Foo.Main.cpp
-> berisi metode utama, titik masuk program. Kode ini membuat Instan Foo dan menggunakannya.Keduanya
Foo.cpp
danMain.cpp
perlu dimasukkanFoo.h
.Foo.cpp
membutuhkannya karena mendefinisikan kode yang mendukung antarmuka kelas, sehingga perlu tahu apa antarmuka itu.Main.cpp
membutuhkannya karena ia menciptakan Foo dan menjalankan perilakunya, sehingga ia harus tahu apa perilakunya, ukuran Foo dalam memori dan bagaimana menemukan fungsinya, dll. tetapi ia belum membutuhkan implementasi yang sebenarnya.Compiler akan menghasilkan
Foo.o
dariFoo.cpp
yang berisi semua kode kelas Foo dalam bentuk yang dikompilasi. Ini juga menghasilkanMain.o
yang mencakup metode utama dan referensi yang tidak terselesaikan ke kelas Foo.Sekarang hadir tautan, yang menggabungkan dua file objek
Foo.o
danMain.o
menjadi file yang dapat dieksekusi. Ia melihat referensi Foo yang belum terselesaikan diMain.o
tetapi melihat yangFoo.o
berisi simbol yang diperlukan, sehingga "menghubungkan titik-titik" untuk berbicara. Panggilan fungsiMain.o
sekarang terhubung ke lokasi aktual dari kode yang dikompilasi sehingga pada saat runtime, program dapat melompat ke lokasi yang benar.Jika Anda memasukkan
Foo.cpp
file keMain.cpp
dalamnya, akan ada dua definisi kelas Foo. Tautan akan melihat ini dan berkata, "Saya tidak tahu yang mana yang harus dipilih, jadi ini adalah kesalahan." Langkah kompilasi akan berhasil, tetapi menghubungkan tidak akan berhasil. (Kecuali Anda tidak mengkompilasiFoo.cpp
tetapi mengapa ia berada di.cpp
file yang terpisah ?)Akhirnya, gagasan tentang jenis file yang berbeda tidak relevan dengan kompiler C / C ++. Ini mengkompilasi "file teks" yang diharapkan mengandung kode yang valid untuk bahasa yang diinginkan. Kadang-kadang mungkin bisa memberi tahu bahasa berdasarkan ekstensi file. Sebagai contoh, kompilasi
.c
file tanpa opsi kompiler dan ia akan menganggap C, sementara ekstensi.cc
atau.cpp
akan memberitahu itu untuk menganggap C ++. Namun, saya dapat dengan mudah memberitahu kompiler untuk mengkompilasi.h
atau bahkan.docx
file sebagai C ++, dan itu akan memancarkan.o
file object ( ) jika mengandung kode C ++ yang valid dalam format teks biasa. Ekstensi ini lebih untuk kepentingan programmer. Jika saya melihatFoo.h
danFoo.cpp
, saya langsung berasumsi bahwa yang pertama berisi deklarasi kelas dan yang kedua berisi definisi.sumber
Foo.cpp
di dalamnyaMain.cpp
Anda tidak perlu memasukkan.h
file, Anda memiliki satu file lebih sedikit, Anda masih menang dalam memecah kode menjadi file terpisah untuk dibaca, dan perintah kompilasi Anda lebih sederhana. Sementara saya memahami pentingnya file header saya tidak berpikir iniIf you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
Kalimat paling penting berkenaan dengan pertanyaan itu.Baca lebih lanjut tentang peran preprocessor C dan C ++ , yang secara konseptual merupakan "fase" pertama dari kompiler C atau C ++ (secara historis itu adalah program yang terpisah
/lib/cpp
; sekarang, untuk alasan kinerja ia terintegrasi di dalam compiler yang tepatcc1
ataucc1plus
). Baca khususnya dokumentasicpp
preprosesor GNU . Jadi, dalam praktiknya, kompiler secara konseptual terlebih dahulu memproses unit kompilasi Anda (atau unit terjemahan ) dan kemudian bekerja pada formulir preproses.Anda mungkin harus selalu menyertakan file header
file.h
jika mengandung (sebagaimana ditentukan oleh konvensi dan kebiasaan ):typedef
.struct
,class
dll, ...)static inline
fungsiPerhatikan bahwa ini adalah masalah konvensi (dan kemudahan) untuk meletakkan ini dalam file header.
Tentu saja, implementasi Anda
file.cpp
membutuhkan semua hal di atas, jadi ingin#include "file.h"
pada awalnya.Ini adalah konvensi (tapi yang sangat umum). Anda dapat menghindari file header dan menyalin dan menempel kontennya ke file implementasi (yaitu unit terjemahan). Tetapi Anda tidak menginginkan itu (kecuali mungkin jika kode C atau C ++ Anda dibuat secara otomatis; maka Anda dapat membuat program generator melakukan copy & paste itu, meniru peran preprosesor).
Intinya adalah bahwa preprosesor melakukan operasi tekstual saja. Anda dapat (pada prinsipnya) menghindarinya sepenuhnya dengan menyalin & menempel, atau menggantinya dengan "preprosesor" lain atau pembuat kode C (seperti gpp atau m4 ).
Masalah tambahan adalah bahwa standar C (atau C ++) baru-baru ini mendefinisikan beberapa header standar. Sebagian besar implementasi benar-benar mengimplementasikan header standar ini sebagai file (khusus implementasi) , tetapi saya percaya bahwa akan mungkin untuk implementasi yang sesuai untuk mengimplementasikan standar termasuk (seperti
#include <stdio.h>
untuk C, atau#include <vector>
untuk C ++) dengan beberapa trik sulap (misalnya menggunakan beberapa basis data atau beberapa informasi di dalam kompiler).Jika menggunakan kompiler GCC (mis.
gcc
Ataug++
) Anda dapat menggunakan-H
flag untuk mendapatkan informasi tentang setiap inklusi, dan-C -E
flag untuk mendapatkan formulir yang telah diproses. Tentu saja ada banyak flag compiler lain yang mempengaruhi preprocessing (mis.-I /some/dir/
Untuk menambahkan/some/dir/
untuk mencari file yang disertakan, dan-D
untuk menentukan beberapa makro preprocessor, dll, dll ....).NB. Versi C ++ di masa depan (mungkin C ++ 20 , bahkan mungkin lebih baru) mungkin memiliki modul C ++ .
sumber
Karena model multi-unit build C ++, Anda memerlukan cara untuk memiliki kode yang muncul di program Anda hanya sekali (definisi), dan Anda perlu cara untuk memiliki kode yang muncul di setiap unit terjemahan program Anda (deklarasi).
Dari sinilah lahir idiom header C ++. Itu konvensi karena suatu alasan.
Anda dapat membuang seluruh program Anda ke dalam satu unit terjemahan, tetapi ini menimbulkan masalah dengan penggunaan kembali kode, pengujian unit dan penanganan ketergantungan antar-modul. Ini juga hanya kekacauan besar.
sumber
Jawaban yang dipilih dari Mengapa kita perlu menulis file header? adalah penjelasan yang masuk akal, tetapi saya ingin menambahkan detail tambahan.
Tampaknya rasional untuk file header cenderung hilang dalam mengajar dan membahas C / C ++.
File header memberikan solusi untuk dua masalah pengembangan aplikasi:
C / C ++ dapat menskala dari program kecil ke baris file multi-juta yang sangat besar, multi-ribu file. Pengembangan aplikasi dapat berkembang dari tim yang terdiri dari satu pengembang hingga ratusan pengembang.
Anda dapat memakai beberapa topi sebagai pengembang. Secara khusus, Anda bisa menjadi pengguna antarmuka untuk fungsi dan kelas, atau Anda bisa menjadi penulis antarmuka fungsi dan kelas.
Saat Anda menggunakan fungsi, Anda harus mengetahui antarmuka fungsi, parameter apa yang digunakan, apa fungsi yang dikembalikan dan Anda perlu tahu apa fungsi itu. Ini mudah didokumentasikan dalam file header tanpa harus melihat implementasinya. Kapan Anda sudah membaca implementasi
printf
? Beli, kami menggunakannya setiap hari.Saat Anda adalah pengembang antarmuka, topi mengubah arah yang lain. File header memberikan deklarasi antarmuka publik. File header menentukan apa yang dibutuhkan implementasi lain untuk menggunakan antarmuka ini. Informasi internal dan pribadi untuk antarmuka baru ini tidak (dan tidak boleh) dideklarasikan dalam file header. File header publik haruslah semua orang perlu menggunakan modul.
Untuk pengembangan skala besar, kompilasi dan penautan dapat memakan waktu lama. Dari beberapa menit hingga beberapa jam (bahkan hingga beberapa hari!). Membagi perangkat lunak menjadi antarmuka (header) dan implementasi (sumber) menyediakan metode untuk hanya mengkompilasi file yang perlu dikompilasi daripada membangun kembali semuanya.
Selain itu, file header memungkinkan pengembang untuk menyediakan perpustakaan (sudah dikompilasi) serta file header. Pengguna lain perpustakaan mungkin tidak pernah melihat implementasi yang tepat, tetapi masih bisa menggunakan perpustakaan dengan file header. Anda melakukan ini setiap hari dengan pustaka standar C / C ++.
Bahkan jika Anda mengembangkan aplikasi kecil, menggunakan teknik pengembangan perangkat lunak skala besar adalah kebiasaan yang baik. Tetapi kita juga perlu mengingat mengapa kita menggunakan kebiasaan ini.
sumber
Anda mungkin juga bertanya mengapa tidak hanya memasukkan semua kode Anda ke dalam satu file.
Jawaban paling sederhana adalah pemeliharaan kode.
Ada kalanya masuk akal untuk membuat kelas:
Waktu yang masuk akal untuk benar-benar sejajar dalam header adalah ketika kelas benar-benar sebuah data struct dengan beberapa pengambil dan setter dasar dan mungkin sebuah konstruktor yang mengambil nilai untuk menginisialisasi anggotanya.
(Templat, yang harus semuanya diuraikan, merupakan masalah yang sedikit berbeda).
Waktu lain untuk membuat kelas semua dalam header adalah ketika Anda mungkin menggunakan kelas dalam beberapa proyek dan secara khusus ingin menghindari tautan di perpustakaan.
Saat Anda dapat menyertakan seluruh kelas dalam unit kompilasi dan sama sekali tidak memaparkan headernya adalah:
Kelas "impl" yang hanya digunakan oleh kelas yang mengimplementasikannya. Ini adalah detail implementasi dari kelas itu, dan secara eksternal tidak digunakan.
Implementasi kelas dasar abstrak yang dibuat oleh semacam metode pabrik yang mengembalikan pointer / referensi / smart pointer) ke kelas dasar. Metode pabrik akan diekspos oleh kelas itu sendiri tidak akan. (Selain itu jika kelas memiliki instance yang mendaftar sendiri di atas meja melalui instance statis, itu bahkan tidak perlu diekspos melalui pabrik).
Tipe kelas "functor".
Dengan kata lain, di mana Anda tidak ingin orang lain memasukkan header.
Saya tahu apa yang mungkin Anda pikirkan ... Jika itu hanya untuk pemeliharaan, dengan memasukkan file cpp (atau header yang benar-benar inline), Anda dapat dengan mudah mengedit file untuk "menemukan" kode dan hanya membangun kembali.
Namun "pemeliharaan" tidak hanya memiliki kode yang terlihat rapi. Ini adalah masalah dampak perubahan. Secara umum diketahui bahwa jika Anda menjaga header tidak berubah dan hanya mengubah
(.cpp)
file implementasi , itu tidak akan diperlukan untuk membangun kembali sumber lain karena seharusnya tidak ada efek samping.Ini membuatnya "lebih aman" untuk membuat perubahan seperti itu tanpa khawatir tentang efek knock-on dan itulah yang sebenarnya dimaksud dengan "perawatan".
sumber
Contoh Snowman perlu sedikit ekstensi untuk menunjukkan dengan tepat mengapa file .h diperlukan.
Tambahkan Bar kelas lain ke dalam permainan yang juga tergantung pada kelas Foo.
Foo.h -> berisi deklarasi untuk kelas Foo
Foo.cpp -> berisi definisi (implementasi) dari kelas Foo
Main.cpp -> menggunakan variabel dari tipe Foo.
Bar.cpp -> menggunakan variabel tipe Foo juga.
Sekarang semua file cpp perlu menyertakan Foo.h Akan menjadi kesalahan untuk menyertakan Foo.cpp di lebih dari satu file cpp lainnya. Linker akan gagal karena kelas Foo akan didefinisikan lebih dari sekali.
sumber
.h
file - dengan menyertakan penjaga dan masalah yang sama persis dengan yang Anda miliki dengan.h
file. Ini bukan untuk apa.h
file sama sekali..cpp
file.Jika Anda menulis seluruh kode dalam file yang sama, maka itu akan membuat kode Anda jelek.
Kedua, Anda tidak akan dapat membagikan kelas tertulis Anda dengan orang lain. Menurut rekayasa perangkat lunak Anda harus menulis kode klien secara terpisah. Klien seharusnya tidak tahu bagaimana program Anda bekerja. Mereka hanya perlu keluaran Jika Anda menulis seluruh program dalam file yang sama itu akan membocorkan keamanan program Anda.
sumber