Apakah ini praktik yang baik untuk menempatkan definisi C ++ dalam file header?

196

Gaya pribadi saya dengan C ++ selalu menempatkan deklarasi kelas dalam file sertakan, dan definisi dalam file .cpp, sangat mirip dengan yang ditentukan dalam jawaban Loki untuk File Header C ++, Pemisahan Kode . Diakui, bagian dari alasan saya suka gaya ini mungkin ada hubungannya dengan semua tahun yang saya habiskan untuk pengkodean Modula-2 dan Ada, yang keduanya memiliki skema serupa dengan spesifikasi file dan file body.

Saya memiliki rekan kerja, jauh lebih berpengetahuan di C ++ daripada saya, yang bersikeras bahwa semua deklarasi C ++ harus, jika memungkinkan, termasuk definisi di sana dalam file header. Dia tidak mengatakan ini adalah gaya alternatif yang valid, atau bahkan gaya yang sedikit lebih baik, tetapi ini adalah gaya baru yang diterima secara universal yang sekarang digunakan semua orang untuk C ++.

Aku tidak lentur seperti dulu, jadi aku tidak benar-benar ingin naik kereta musiknya sampai aku melihat beberapa orang di sana bersamanya. Jadi seberapa umum idiom ini?

Sekedar memberi struktur pada jawaban: Apakah sekarang The Way , sangat umum, agak umum, tidak umum, atau gila?

TED
sumber
fungsi satu baris (getter dan setter) di header adalah umum. Lebih lama daripada akan mendapatkan pandangan kedua yang aneh. Mungkin untuk definisi lengkap dari kelas kecil yang hanya digunakan oleh yang lain di header yang sama?
Martin Beckett
saya selalu menempatkan semua definisi kelas saya di header sejauh ini. hanya definisi untuk kelas pimpl yang merupakan pengecualian. saya hanya mendeklarasikan mereka di header.
Johannes Schaub - litb
3
Mungkin dia berpikir seperti itu karena itulah bagaimana Visual C ++ menegaskan bahwa kode harus ditulis. Ketika Anda mengklik tombol, implementasi dihasilkan dalam file header. Saya tidak tahu mengapa Microsoft akan mendorong ini karena alasan yang dijelaskan orang lain di bawah ini.
WKS
4
@WKS - Microsoft lebih suka semua orang memprogram dalam C #, dan dalam C #, tidak ada perbedaan "header" vs "body", itu hanya satu file. Setelah berada di dunia C ++ dan C # untuk waktu yang lama sekarang, cara C # sebenarnya jauh lebih mudah untuk dihadapi.
Mark Lakata
1
@MarkLakata - Itu memang salah satu hal yang dia tunjukkan. Saya belum mendengar argumen ini darinya akhir-akhir ini, tetapi IIRC dia berpendapat bahwa Java dan C # bekerja dengan cara ini, dan C # adalah merek baru pada saat itu, yang membuatnya menjadi tren semua bahasa akan segera menyusul
TED

Jawaban:

209

Rekan kerja Anda salah, cara umum adalah dan selalu menempatkan kode dalam file .cpp (atau ekstensi apa pun yang Anda suka) dan deklarasi dalam header.

Kadang-kadang ada beberapa manfaat untuk menempatkan kode di header, ini dapat memungkinkan inlining lebih pintar oleh kompiler. Tetapi pada saat yang sama, ini dapat menghancurkan waktu kompilasi Anda karena semua kode harus diproses setiap kali dimasukkan oleh kompiler.

Akhirnya, sering menjengkelkan memiliki hubungan objek melingkar (kadang-kadang diinginkan) ketika semua kode adalah header.

Intinya, Anda benar, dia salah.

EDIT: Saya telah memikirkan pertanyaan Anda. Ada satu kasus di mana apa yang dia katakan itu benar. template. Banyak pustaka "modern" yang lebih baru seperti boost yang banyak menggunakan templat dan seringkali hanya "header". Namun, ini hanya boleh dilakukan ketika berhadapan dengan template karena itu adalah satu-satunya cara untuk melakukannya ketika berhadapan dengan template.

EDIT: Beberapa orang ingin sedikit lebih banyak klarifikasi, berikut beberapa pemikiran untuk menulis kode "hanya header":

Jika Anda mencari di sekitar, Anda akan melihat cukup banyak orang mencoba menemukan cara untuk mengurangi waktu kompilasi ketika berhadapan dengan dorongan. Misalnya: Cara mengurangi waktu kompilasi dengan Boost Asio , yaitu melihat kompilasi 14 file tunggal 1K dengan tambahan yang disertakan. 14s mungkin tidak tampak "meledak", tetapi tentu jauh lebih lama dari biasanya dan dapat bertambah dengan cepat. Saat berhadapan dengan proyek besar. Hanya pustaka header yang memengaruhi waktu kompilasi dengan cara yang cukup terukur. Kami hanya mentolerirnya karena peningkatan sangat berguna.

Selain itu, ada banyak hal yang tidak dapat dilakukan di header saja (bahkan meningkatkan memiliki perpustakaan Anda perlu menautkan ke bagian-bagian tertentu seperti utas, sistem file, dll). Contoh utama adalah bahwa Anda tidak dapat memiliki objek global sederhana di header hanya libs (kecuali Anda menggunakan kekejian yang merupakan singleton) karena Anda akan mengalami beberapa kesalahan definisi. CATATAN: Variabel inline C ++ 17 akan membuat contoh khusus ini bisa dilakukan di masa depan.

Sebagai poin terakhir, ketika menggunakan boost sebagai contoh kode hanya header, detail besar sering terlewatkan.

Boost adalah pustaka, bukan kode tingkat pengguna. jadi itu tidak sering berubah. Dalam kode pengguna, jika Anda meletakkan semuanya di header, setiap perubahan kecil akan menyebabkan Anda harus mengkompilasi ulang seluruh proyek. Itu buang-buang waktu yang monumental (dan bukan untuk perpustakaan yang tidak berubah dari kompilasi ke kompilasi). Ketika Anda membagi hal-hal antara header / sumber dan yang lebih baik lagi, gunakan deklarasi maju untuk mengurangi termasuk, Anda dapat menghemat waktu mengkompilasi ulang ketika ditambahkan dalam sehari.

Evan Teran
sumber
16
Saya cukup yakin dari sanalah dia mendapatkannya. Setiap kali ini muncul, ia menampilkan template. Argumennya kira-kira bahwa Anda harus melakukan semua kode dengan cara ini untuk konsistensi.
TED
14
itu adalah argumen yang buruk dia membuat, tongkat untuk senjata Anda :)
Evan Teran
11
Definisi template dapat dalam file CPP jika kata kunci "ekspor" didukung. Itu sudut gelap C ++ yang biasanya bahkan tidak diimplementasikan oleh sebagian besar kompilasi, sejauh yang saya tahu.
Andrei Taranchenko
2
Lihat bagian bawah jawaban ini (bagian atas agak berbelit-belit) untuk contoh: stackoverflow.com/questions/555330/…
Evan Teran
3
Itu mulai bermakna untuk diskusi ini di "Hore, tidak ada kesalahan linker."
Evan Teran
158

Pada hari C ++ coders menyetujui The Way , domba akan berbaring dengan singa, Palestina akan merangkul orang Israel, dan kucing dan anjing akan diizinkan untuk menikah.

Pemisahan antara file h. Menurut saya, deklarasi berada di header dan definisi termasuk dalam file implementasi. Tapi, itu hanya kebiasaan, bukan agama.

Ya - Jake itu.
sumber
140
"Hari C ++ coders menyetujui The Way ..." hanya akan ada satu C ++ coder tersisa!
Brian Ensink
9
Saya pikir mereka sudah setuju dalam perjalanan, deklarasi di H dan definisi dalam cpp
hasen
6
Kita semua adalah orang buta dan C ++ adalah gajah.
Roderick Taylor
kebiasaan? jadi bagaimana dengan menggunakan .h untuk mendefinisikan ruang lingkup? dengan hal apa telah diganti?
Hernán Eche
28

Code in header umumnya merupakan ide yang buruk karena memaksa kompilasi ulang semua file yang menyertakan header ketika Anda mengubah kode aktual daripada deklarasi. Ini juga akan memperlambat kompilasi karena Anda harus mem-parsing kode di setiap file yang menyertakan header.

Alasan untuk memiliki kode dalam file header adalah umumnya diperlukan agar kata kunci inline berfungsi dengan benar dan ketika menggunakan template yang sedang dipasang pada file cpp lainnya.

Laserallan
sumber
1
"Ini memaksa kompilasi ulang semua file yang menyertakan header ketika Anda mengubah kode aktual daripada deklarasi" Saya pikir ini adalah alasan paling asli; juga berjalan dengan fakta bahwa deklarasi di header berubah lebih jarang daripada implementasi dalam file .c.
Ninad
20

Apa yang mungkin memberi tahu Anda rekan kerja adalah gagasan bahwa sebagian besar kode C ++ harus dibuat untuk memungkinkan kegunaan maksimum. Dan jika itu templated, maka semuanya harus ada dalam file header, sehingga kode klien dapat melihatnya dan membuat instance. Jika cukup baik untuk Boost dan STL, itu cukup baik untuk kita.

Saya tidak setuju dengan sudut pandang ini, tetapi mungkin dari mana asalnya.

JohnMcG
sumber
Saya pikir Anda benar tentang ini. Ketika kita membahasnya, dia selalu menggunakan contoh templat, di mana Anda lebih atau kurang harus melakukan ini. Saya tidak setuju dengan "harus" juga, tetapi alternatif saya agak berbelit-belit.
TED
1
@ted - untuk kode templated Anda harus meletakkan implementasinya di header. Kata kunci 'ekspor' memungkinkan kompiler untuk mendukung pemisahan deklarasi dan definisi templat, tetapi dukungan untuk ekspor sama sekali tidak ada. anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1426.pdf
Michael Burr
Sebuah header, ya, tetapi tidak harus header yang sama. Lihat jawaban yang tidak diketahui di bawah ini.
TED
Itu masuk akal, tapi saya tidak bisa mengatakan saya pernah menemukan gaya itu sebelumnya.
Michael Burr
14

Saya pikir rekan kerja Anda cerdas dan Anda juga benar.

Hal-hal berguna yang saya temukan bahwa meletakkan segala sesuatu di header adalah:

  1. Tidak perlu menulis & menyinkronkan header dan sumber.

  2. Strukturnya polos dan tidak ada ketergantungan melingkar yang memaksa pembuat kode untuk membuat struktur yang "lebih baik".

  3. Mudah dibawa, mudah disematkan ke proyek baru.

Saya setuju dengan masalah waktu kompilasi, tetapi saya pikir kita harus memperhatikan bahwa:

  1. Perubahan file sumber sangat mungkin untuk mengubah file header yang menyebabkan seluruh proyek dikompilasi ulang.

  2. Kecepatan kompilasi jauh lebih cepat dari sebelumnya. Dan jika Anda memiliki proyek yang akan dibangun dengan waktu yang lama dan frekuensi tinggi, itu mungkin menunjukkan bahwa desain proyek Anda memiliki kekurangan. Memisahkan tugas ke dalam proyek dan modul yang berbeda dapat menghindari masalah ini.

Terakhir saya hanya ingin mendukung rekan kerja Anda, hanya dalam pandangan pribadi saya.

Bin XU
sumber
3
+1. Tidak seorang pun kecuali Anda memiliki gagasan bahwa dalam sebuah header hanya memproyeksikan waktu kompilasi yang lama mungkin mengisyaratkan terlalu banyak dependensi yang merupakan desain yang buruk. Poin bagus! Tetapi bisakah ketergantungan ini dihapus sampai batas di mana waktu kompilasi sebenarnya singkat?
TobiMcNamobi
@TobiMcNamobi: Saya suka ide "malas" untuk mendapatkan umpan balik yang lebih baik pada keputusan desain yang buruk. Namun dalam kasus header-only vs dikompilasi secara terpisah, jika kita menyetujui ide itu kita berakhir dengan satu unit kompilasi dan waktu kompilasi yang besar. Bahkan ketika desainnya benar-benar hebat.
Jo So
Dengan kata lain, pemisahan antara antarmuka dan implementasi sebenarnya merupakan bagian dari desain Anda. Dalam C, Anda diminta untuk mengekspresikan keputusan Anda tentang enkapsulasi melalui pemisahan dalam header dan implementasi.
Jo So
1
Saya mulai bertanya-tanya apakah ada kekurangan sama sekali untuk hanya membuang header sama seperti yang dilakukan bahasa modern.
Jo So
12

Seringkali saya akan memasukkan fungsi anggota yang sepele ke dalam file header, untuk memungkinkan mereka diuraikan. Tetapi untuk menempatkan seluruh tubuh kode di sana, hanya agar konsisten dengan template? Itu kacang biasa.

Ingat: Konsistensi bodoh adalah hobgoblin pikiran kecil .

Mark tebusan
sumber
Ya, saya juga melakukannya. Aturan umum yang saya gunakan tampaknya adalah sesuatu di sepanjang baris "jika cocok pada satu baris kode, biarkan di header".
TED
Apa yang terjadi ketika perpustakaan menyediakan tubuh kelas templat A<B>dalam file cpp, dan kemudian pengguna menginginkannya A<C>?
jww
@ jww Saya tidak menyatakannya secara eksplisit, tetapi kelas template harus sepenuhnya ditentukan di header sehingga kompiler dapat instantiate dengan jenis apa pun yang dibutuhkan. Itu persyaratan teknis, bukan pilihan gaya. Saya pikir masalah dalam pertanyaan awal adalah bahwa seseorang memutuskan apakah itu baik untuk templat, itu baik untuk kelas reguler juga.
Mark Ransom
7

Seperti kata Tuomas, tajuk Anda harus minimal. Agar lengkap saya akan sedikit memperluas.

Saya pribadi menggunakan 4 jenis file dalam C++proyek saya :

  • Publik:
  • Penerusan tajuk: jika ada templat dll, file ini mendapatkan deklarasi penerusan yang akan muncul di tajuk.
  • Header: file ini termasuk header penerusan, jika ada, dan menyatakan semua yang saya ingin publik (dan mendefinisikan kelas ...)
  • Pribadi:
  • Header pribadi: file ini adalah header yang disediakan untuk implementasi, termasuk header dan menyatakan fungsi / struktur pembantu (untuk Pimpl misalnya atau predikat). Lewati jika tidak perlu.
  • File sumber: termasuk header pribadi (atau header jika tidak ada header pribadi) dan mendefinisikan semuanya (non-templat ...)

Selanjutnya, saya memasangkan ini dengan aturan lain: Jangan mendefinisikan apa yang dapat Anda sampaikan. Meskipun tentu saja saya masuk akal di sana (menggunakan Pimpl di mana-mana cukup merepotkan).

Itu berarti bahwa saya lebih suka deklarasi maju daripada #includearahan di header saya setiap kali saya bisa lolos dengan mereka.

Akhirnya, saya juga menggunakan aturan visibilitas: Saya membatasi cakupan simbol saya sebanyak mungkin sehingga mereka tidak mencemari cakupan luar.

Secara keseluruhan:

// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;

// example.hpp
#include "project/example_fwd.hpp"

// Those can't really be skipped
#include <string>
#include <vector>

#include "project/pimpl.hpp"

// Those can be forward declared easily
#include "project/foo_fwd.hpp"

namespace project { class Bar; }

namespace project
{
  class MyClass
  {
  public:
    struct Color // Limiting scope of enum
    {
      enum type { Red, Orange, Green };
    };
    typedef Color::type Color_t;

  public:
    MyClass(); // because of pimpl, I need to define the constructor

  private:
    struct Impl;
    pimpl<Impl> mImpl; // I won't describe pimpl here :p
  };

  template <class T> class MyClassT: public MyClass {};
} // namespace project

// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"

template <class T> void check(MyClass<T> const& c) { }

// example.cpp
#include "example_impl.hpp"

// MyClass definition

Penyelamat di sini adalah bahwa sebagian besar header tidak berguna: hanya diperlukan jika typedefatau templatebegitu juga header implementasi;)

Matthieu M.
sumber
6

Untuk menambahkan lebih banyak kesenangan, Anda dapat menambahkan .ippfile yang berisi implementasi templat (yang termasuk dalam .hpp), sambil .hppberisi antarmuka.

Selain kode templatized (tergantung pada proyek ini dapat berupa file mayoritas atau minoritas) ada kode normal dan di sini lebih baik untuk memisahkan deklarasi dan definisi. Berikan juga deklarasi maju jika diperlukan - ini mungkin berpengaruh pada waktu kompilasi.

Anonim
sumber
Itulah yang saya lakukan dengan definisi template juga (walaupun saya tidak yakin saya menggunakan ekstensi yang sama ... sudah lama).
TED
5

Secara umum, ketika menulis kelas baru, saya akan meletakkan semua kode di kelas, jadi saya tidak perlu mencari di file lain untuk itu .. Setelah semuanya bekerja, saya memecah tubuh metode ke dalam file cpp , meninggalkan prototipe dalam file hpp.

EvilTeach
sumber
4

Jika cara baru ini benar - benar Cara , kami mungkin telah berlari ke arah yang berbeda dalam proyek kami.

Karena kami mencoba menghindari semua hal yang tidak perlu dalam tajuk. Itu termasuk menghindari kaskade header. Kode dalam header mungkin perlu beberapa header lainnya untuk dimasukkan, yang akan membutuhkan header lain dan seterusnya. Jika kami terpaksa menggunakan templat, kami mencoba menghindari header sembarangan dengan hal-hal templat terlalu banyak.

Kami juga menggunakan "opaque pointer" -pattern saat berlaku.

Dengan praktik ini, kami dapat melakukan pembangunan lebih cepat daripada kebanyakan rekan kerja kami. Dan ya ... mengubah kode atau anggota kelas tidak akan menyebabkan pembangunan kembali yang besar.

Virne
sumber
4

Saya pribadi melakukan ini di file header saya:

// class-declaration

// inline-method-declarations

Saya tidak suka mencampur kode untuk metode dengan kelas karena saya merasa sulit untuk melihat semuanya dengan cepat.

Saya tidak akan menempatkan SEMUA metode dalam file header. Compiler akan (biasanya) tidak dapat inline metode virtual dan akan (kemungkinan) hanya inline metode kecil tanpa loop (benar-benar tergantung pada kompiler).

Melakukan metode di kelas adalah valid ... tetapi dari sudut pandang yang dapat dibaca saya tidak menyukainya. Menempatkan metode di header berarti bahwa, jika memungkinkan, mereka akan mendapatkan inline.

TofuBeer
sumber
2

IMHO, Dia pantas HANYA jika dia melakukan template dan / atau metaprogramming. Ada banyak alasan yang telah disebutkan bahwa Anda membatasi file header hanya untuk deklarasi. Itu hanya ... header. Jika Anda ingin memasukkan kode, kompilasi sebagai pustaka dan tautkan.

spoulson
sumber
2

Saya meletakkan semua implementasi dari definisi kelas. Saya ingin mengeluarkan komentar doxygen dari definisi kelas.

Jesus Fernandez
sumber
1
Saya tahu ini terlambat, tetapi para downvoter (atau simpatisan) peduli untuk berkomentar mengapa? Ini sepertinya pernyataan yang masuk akal bagi saya. Kami menggunakan Doxygen, dan masalah pasti muncul.
TED
2

Saya pikir itu benar-benar tidak masuk akal untuk menempatkan SEMUA definisi fungsi Anda ke file header. Mengapa? Karena file header digunakan sebagai antarmuka PUBLIC ke kelas Anda. Ini bagian luar "kotak hitam".

Ketika Anda perlu melihat kelas untuk referensi bagaimana menggunakannya, Anda harus melihat file header. File header harus memberikan daftar apa yang dapat dilakukan (dikomentari untuk menjelaskan detail tentang bagaimana menggunakan setiap fungsi), dan itu harus menyertakan daftar variabel anggota. HARUS TIDAK termasuk BAGAIMANA fungsi masing-masing individu diimplementasikan, karena itu memuat informasi yang tidak perlu dan hanya mengacaukan file header.

SeanRamey
sumber
1

Bukankah itu benar-benar tergantung pada kompleksitas sistem, dan konvensi in-house?

Saat ini saya sedang mengerjakan simulator jaringan saraf yang sangat kompleks, dan gaya yang diterima yang saya harapkan untuk digunakan adalah:

Definisi kelas di classname.h
Kode kelas di classnameCode.h
kode yang dapat dieksekusi di classname.cpp

Ini memecah simulasi yang dibuat pengguna dari kelas dasar yang dibangun oleh pengembang, dan bekerja paling baik dalam situasi tersebut.

Namun, saya akan terkejut melihat orang-orang melakukan ini, katakanlah, aplikasi grafis, atau aplikasi lain yang tujuannya bukan untuk memberi pengguna basis kode.

Ed James
sumber
1
Apa sebenarnya perbedaan antara "kode kelas" dan "kode yang dapat dieksekusi"?
TED
Seperti yang saya katakan, ini adalah simulator saraf: Pengguna membuat simulasi yang dapat dieksekusi yang dibangun di atas sejumlah besar kelas yang bertindak sebagai neuron, dll. Jadi kode kami hanyalah kelas yang sebenarnya tidak dapat melakukan apa pun sendiri, dan pengguna membuat kode yang dapat dieksekusi yang membuat simulator melakukan hal-hal.
Ed James
Secara umum, tidak bisakah Anda mengatakan "sebenarnya tidak dapat melakukan apa pun dengan sendirinya" untuk sebagian besar (jika tidak keseluruhan) dari sebagian besar program apa pun? Apakah Anda mengatakan bahwa kode "utama" menggunakan cpp, tetapi tidak ada yang lain?
TED
Dalam situasi ini agak berbeda. Kode yang kita tulis pada dasarnya adalah pustaka, dan pengguna membangun simulasi mereka di atas ini, yang sebenarnya bisa dijalankan. Pikirkan itu seperti openGL -> Anda mendapatkan banyak fungsi dan objek tetapi tanpa file cpp yang dapat menjalankannya, mereka tidak berguna.
Ed James
0

Kode templat harus dalam tajuk saja. Terlepas dari itu semua definisi kecuali inline harus dalam .cpp. Argumen terbaik untuk ini adalah implementasi perpustakaan std yang mengikuti aturan yang sama. Anda tidak akan tidak setuju para pengembang std lib akan benar mengenai hal ini.

Abhishek Agarwal
sumber
Stdlibs yang mana ? libstdc++Tampaknya GCC (AFAICS) hampir tidak memasukkan apa pun di src& hampir semuanya include, baik itu 'harus' ada di header. Jadi saya tidak berpikir ini kutipan yang akurat / berguna. Ngomong-ngomong, saya tidak berpikir stdlibs banyak model untuk kode pengguna: mereka jelas ditulis oleh coders yang sangat terampil, tetapi untuk digunakan , tidak membaca: mereka abstrak kompleksitas tinggi yang kebanyakan coders tidak perlu memikirkan , perlu jelek di _Reserved __namesmana-mana untuk menghindari konflik dengan pengguna, komentar & spasi di bawah apa yang saya sarankan, dll. Mereka patut dicontoh dengan cara yang sempit.
underscore_d
0

Saya pikir rekan kerja Anda benar selama dia tidak masuk dalam proses untuk menulis kode yang dapat dieksekusi di header. Keseimbangan yang tepat, saya pikir, adalah mengikuti jalur yang ditunjukkan oleh GNAT Ada di mana file .ads memberikan definisi antarmuka yang sangat memadai dari paket untuk penggunanya dan untuk anak-anaknya.

Ngomong-ngomong, Ted, pernahkah Anda melihat di forum ini pertanyaan terakhir tentang Ada yang mengikat ke perpustakaan CLIPS yang Anda tulis beberapa tahun lalu dan yang tidak tersedia lagi (halaman Web yang relevan sekarang ditutup). Bahkan jika dibuat untuk versi Klip lama, ikatan ini bisa menjadi contoh awal yang baik bagi seseorang yang bersedia menggunakan mesin inferensi CLIPS dalam program Ada 2012.

Emile
sumber
1
Lol. 2 tahun kemudian, ini adalah cara aneh untuk menghubungi seseorang. Saya akan memeriksa apakah saya masih memiliki salinan, tetapi kemungkinan besar tidak. Saya melakukan itu untuk kelas AI sehingga saya bisa melakukan kode saya di Ada, tetapi dengan sengaja membuat proyek CC0 (pada dasarnya tanpa hak cipta) dengan harapan seseorang tanpa malu-malu akan mengambilnya dan melakukan sesuatu dengannya.
TED