Cara untuk mengatur antarmuka dan implementasi di C ++

12

Saya telah melihat bahwa ada beberapa paradigma yang berbeda dalam C ++ tentang apa yang masuk ke file header dan apa ke file cpp. AFAIK, kebanyakan orang, terutama yang berlatar belakang C, melakukan:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

Namun, dosen saya biasanya mengajar C ++ kepada pemula seperti ini:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Awalnya berasal dari Jawa, saya juga selalu terjebak dengan cara kedua ini karena beberapa alasan, seperti bahwa saya hanya perlu mengubah sesuatu di satu tempat jika antarmuka atau nama metode berubah, bahwa saya suka lekukan yang berbeda dari hal-hal di kelas ketika saya lihat implementasinya, dan saya menemukan nama lebih mudah dibaca foodibandingkan dengan foo::foo.

Saya ingin mengumpulkan pro dan kontra untuk kedua cara. Mungkin masih ada cara lain?

Salah satu kelemahan dari cara saya tentu saja adalah kebutuhan untuk deklarasi maju sesekali.

Felix Dombek
sumber
2
foo.cppsekarang tidak ada hubungannya dengan fookelas Anda dan harus dibiarkan kosong (mungkin tetapi #includeuntuk membuat sistem build Anda bahagia).
Benjamin Bannier
2
Dosen Anda gila.
Lightness Races in Orbit

Jawaban:

16

Sementara versi kedua lebih mudah untuk menulis, itu mencampur antarmuka dengan implementasi.

File sumber yang menyertakan file header perlu dikompilasi ulang setiap kali file header diubah. Di versi pertama Anda hanya akan mengubah file header jika Anda perlu mengubah antarmuka. Di versi kedua Anda akan mengubah file header jika Anda perlu mengubah antarmuka atau implementasinya.

Selain itu Anda tidak boleh mengekspos detail implementasi , Anda akan mendapatkan kompilasi ulang yang tidak perlu dengan versi kedua.

Program Lenny
sumber
1
+1 Profiler saya tidak memasukkan kode yang ditempatkan di file header - itu alasan yang berharga juga.
Eugene
Jika Anda melihat jawaban saya untuk programer pertanyaan ini.stackexchange.com/questions/4573/... Anda akan melihat bagaimana itu sangat tergantung pada semantik kelas, yaitu apa yang akan menggunakannya (khususnya jika itu adalah bagian yang terbuka dari antarmuka pengguna Anda dan berapa banyak kelas lain di sistem Anda yang menggunakannya secara langsung).
CashCow
3

Saya melakukannya dengan cara kedua di tahun '93 -95. Butuh beberapa menit untuk mengkompilasi ulang aplikasi kecil dengan 5-10 fungsi / file (pada PC 486 yang sama .. dan tidak, saya juga tidak tahu tentang kelas, saya baru berusia 14-15 tahun dan tidak ada internet ) .

Jadi, apa yang Anda ajarkan kepada pemula dan apa yang Anda gunakan secara profesional adalah teknik yang sangat berbeda, terutama di C ++.

Saya pikir perbandingan antara C ++ dan mobil F1 sangat tepat. Anda tidak memasukkan pemula ke dalam mobil F1 (yang bahkan tidak dapat dinyalakan kecuali Anda memanaskan mesin hingga 80-95 derajat celcius).

Jangan mengajarkan C ++ sebagai bahasa pertama. Anda harus cukup berpengalaman untuk mengetahui mengapa opsi 2 lebih buruk daripada opsi 1 secara umum, tahu sedikit apa artinya kompilasi / penghubungan statis dan dengan demikian memahami mengapa C ++ lebih memilihnya dengan cara pertama.

Macke
sumber
Jawaban ini akan lebih baik jika Anda menguraikan sedikit tentang kompilasi statis / menghubungkan (saya tidak tahu saat itu!)
Felix Dombek
2

Metode kedua adalah apa yang saya sebut kelas yang sepenuhnya inline. Anda sedang menulis definisi kelas tetapi semua kode Anda menggunakannya hanya akan sebaris kode.

Ya, kompiler memutuskan kapan harus inline dan kapan tidak ... Dalam hal ini Anda membantu kompilator membuat keputusan, dan Anda berpotensi akan menghasilkan kode yang lebih sedikit dan berpotensi lebih cepat.

Keuntungan ini kemungkinan lebih besar daripada fakta bahwa jika Anda memodifikasi implementasi suatu fungsi, Anda perlu membangun kembali semua sumber yang menggunakannya. Dalam kelas yang ringan, Anda tidak akan memodifikasi implementasinya. Jika Anda menambahkan metode baru, Anda harus memodifikasi tajuk.

Ketika kelas Anda menjadi lebih kompleks, meskipun menambahkan loop, manfaat melakukannya dengan cara ini turun.

Itu masih memiliki kelebihan, khususnya:

  • Jika ini adalah kode "fungsi umum", Anda cukup memasukkan header dan menggunakannya dari beberapa proyek tanpa harus menautkan ke pustaka yang berisi sumbernya.

Kelemahan dari inlining menjadi masalah ketika itu berarti Anda harus memasukkan spesifikasi implementasi ke header Anda, yaitu Anda harus mulai memasukkan header tambahan.

Perhatikan bahwa templat adalah kasus khusus karena Anda harus menyertakan detail implementasi. Anda mungkin mengaburkannya di file lain tetapi harus ada di sana. (Ada pengecualian untuk aturan itu dengan instantiations tetapi secara umum Anda inline template Anda).

Uang tunai
sumber
1

Ini mungkin tidak signifikan, atau benar jika eksekusi Anda menjadi lebih besar, tetapi lebih banyak kode dalam file header memungkinkan kompiler lebih banyak peluang untuk mengoptimalkan kecepatan.

Jika Anda memutuskan untuk menulis pustaka header saja , topik ini hanyalah salah satu perhatian Anda.

davidvandebunte
sumber