Biasanya ketika mendeklarasikan kelas C ++, itu adalah praktik terbaik untuk hanya menempatkan deklarasi dalam file header dan meletakkan implementasinya dalam file sumber. Namun, tampaknya model desain ini tidak berfungsi untuk kelas templat.
Saat mencari online, tampaknya ada 2 pendapat tentang cara terbaik mengelola kelas templat:
1. Seluruh deklarasi dan implementasi di header.
Ini cukup mudah tetapi mengarah pada apa yang, menurut pendapat saya, sulit untuk mempertahankan dan mengedit file kode ketika template menjadi besar.
2. Tulis implementasinya dalam templat include file (.tpp) yang disertakan di bagian akhir.
Ini sepertinya solusi yang lebih baik bagi saya tetapi tampaknya tidak diterapkan secara luas. Apakah ada alasan mengapa pendekatan ini lebih rendah?
Saya tahu bahwa berkali-kali gaya kode ditentukan oleh preferensi pribadi atau gaya lama. Saya memulai proyek baru (memindahkan proyek C lama ke C ++) dan saya relatif baru dalam desain OO dan ingin mengikuti praktik terbaik dari awal.
sumber
Jawaban:
Saat menulis kelas C ++ templated, Anda biasanya memiliki tiga opsi:
(1) Masukkan deklarasi dan definisi di header.
atau
Pro:
Menipu:
Foo
sebagai anggota, Anda harus menyertakanfoo.h
. Ini berarti bahwa mengubah implementasiFoo::f
propagasi melalui file header dan sumber.Mari kita melihat lebih dekat pada dampak rekondisi: Untuk kelas-kelas C ++ non-templated, Anda menempatkan deklarasi dalam .h dan definisi metode dalam .cpp. Dengan cara ini, ketika implementasi suatu metode diubah, hanya satu .cpp yang perlu dikompilasi ulang. Ini berbeda untuk kelas templat jika .h berisi semua kode Anda. Lihatlah contoh berikut:
Di sini, satu-satunya penggunaan
Foo::f
di dalambar.cpp
. Namun, jika Anda mengubah implementasiFoo::f
, keduanyabar.cpp
danqux.cpp
perlu dikompilasi ulang. ImplementasiFoo::f
kehidupan di kedua file, meskipun tidak ada bagian dariQux
langsung menggunakan apa punFoo::f
. Untuk proyek-proyek besar, ini bisa segera menjadi masalah.(2) Masukkan deklarasi dalam .h dan definisi dalam .tpp dan sertakan dalam .h.
Pro:
Menipu:
Solusi ini memisahkan deklarasi dan definisi metode dalam dua file terpisah, sama seperti .h / .cpp. Namun, pendekatan ini memiliki masalah pembangunan kembali yang sama dengan (1) , karena header langsung memasukkan definisi metode.
(3) Masukkan deklarasi dalam .h dan definisi dalam .tpp, tapi jangan sertakan .tpp dalam .h.
Pro:
Menipu:
Foo
anggota ke kelasBar
, Anda harus memasukkanfoo.h
dalam header. Jika Anda memanggilFoo::f
.cpp, Anda juga harus memasukkannyafoo.tpp
.Pendekatan ini mengurangi dampak pembangunan kembali, karena hanya file .cpp yang benar-benar digunakan
Foo::f
perlu dikompilasi ulang. Namun, ini ada harganya: Semua file itu perlu disertakanfoo.tpp
. Ambil contoh dari atas dan gunakan pendekatan baru:Seperti yang Anda lihat, satu-satunya perbedaan adalah tambahan termasuk
foo.tpp
dalambar.cpp
. Ini tidak nyaman dan menambahkan menyertakan kedua untuk kelas tergantung pada apakah Anda memanggil metode itu tampak sangat jelek. Namun, Anda mengurangi dampak pembangunan kembali: Hanyabar.cpp
perlu dikompilasi ulang jika Anda mengubah implementasiFoo::f
. Filequx.cpp
tidak perlu dikompilasi ulang.Ringkasan:
Jika Anda menerapkan pustaka, Anda biasanya tidak perlu peduli untuk membangun kembali dampak. Pengguna perpustakaan Anda mengambil rilis dan menggunakannya dan implementasi perpustakaan tidak berubah dalam pekerjaan sehari-hari pengguna. Dalam kasus seperti itu, perpustakaan dapat menggunakan pendekatan (1) atau (2) dan itu hanya masalah selera mana yang Anda pilih.
Namun, jika Anda mengerjakan aplikasi, atau jika Anda bekerja di perpustakaan internal perusahaan Anda, kode sering berubah. Jadi, Anda harus peduli tentang dampak pembangunan kembali. Memilih pendekatan (3) bisa menjadi pilihan yang baik jika Anda membuat pengembang Anda menerima tambahan yang disertakan.
sumber
Mirip dengan
.tpp
ide (yang belum pernah saya lihat digunakan), kami menempatkan sebagian besar fungsi inline ke dalam-inl.hpp
file yang termasuk di akhir.hpp
file biasa .Seperti yang ditunjukkan orang lain, ini membuat antarmuka dapat dibaca dengan memindahkan kekacauan implementasi inline (seperti templat) di file lain. Kami mengizinkan beberapa inline antarmuka tetapi mencoba membatasi mereka untuk fungsi-fungsi garis kecil, biasanya tunggal.
sumber
Satu koin pro dari varian ke-2 adalah tajuk Anda terlihat lebih rapi.
Con mungkin adalah Anda mungkin memiliki pengecekan kesalahan IDE inline, dan binding debugger kacau.
sumber
Saya sangat suka pendekatan menempatkan implementasi dalam file terpisah, dan hanya memiliki dokumentasi dan deklarasi dalam file header.
Mungkin alasan Anda belum melihat pendekatan ini banyak digunakan dalam praktik, adalah Anda belum melihat di tempat yang tepat ;-)
Atau - mungkin karena butuh sedikit usaha ekstra dalam mengembangkan perangkat lunak. Tetapi untuk perpustakaan kelas, upaya itu bernilai WELL sementara, IMHO, dan membayar sendiri dalam perpustakaan yang jauh lebih mudah digunakan / dibaca.
Ambil perpustakaan ini sebagai contoh: https://github.com/SophistSolutions/Stroika/
Seluruh perpustakaan ditulis dengan pendekatan ini dan jika Anda melihat melalui kode, Anda akan melihat seberapa baik kerjanya.
File header kira-kira sepanjang file implementasi, tetapi mereka tidak berisi apa-apa selain deklarasi dan dokumentasi.
Bandingkan keterbacaan Stroika dengan implementasi std c ++ favorit Anda (gcc atau libc ++ atau msvc). Mereka semua menggunakan pendekatan implementasi in-header inline, dan meskipun mereka ditulis dengan sangat baik, IMHO, bukan sebagai implementasi yang dapat dibaca.
sumber