Istilah "antarmuka" dalam C ++

11

Java membuat perbedaan yang jelas antara classdan interface. (Saya percaya C # juga, tapi saya tidak punya pengalaman dengannya). Namun ketika menulis C ++ tidak ada perbedaan yang dipaksakan bahasa antara kelas dan antarmuka.

Akibatnya saya selalu melihat antarmuka sebagai solusi untuk kurangnya pewarisan berganda di Jawa. Membuat perbedaan seperti itu terasa sewenang-wenang dan tidak berarti dalam C ++.

Saya selalu cenderung pergi dengan pendekatan "menulis hal-hal dengan cara yang paling jelas", jadi jika dalam C ++ saya punya apa yang disebut antarmuka di Jawa, misalnya:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() = 0;
};

dan saya kemudian memutuskan bahwa sebagian besar pelaksana Fooingin berbagi beberapa fungsi umum yang mungkin akan saya tulis:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() {}
protected:
  // If it needs this to do its thing:
  int internalHelperThing(int);
  // Or if it doesn't need the this pointer:
  static int someOtherHelper(int);
};

Yang kemudian membuat ini bukan antarmuka dalam arti Java lagi.

Sebaliknya C ++ memiliki dua konsep penting, terkait dengan masalah warisan yang mendasari yang sama:

  1. virtual warisan
  2. Kelas tanpa variabel anggota tidak dapat menempati ruang tambahan saat digunakan sebagai basis

    "Subobjek kelas dasar mungkin memiliki ukuran nol"

    Referensi

Di antara yang saya coba hindari # 1 sedapat mungkin - jarang terjadi skenario di mana yang benar-benar merupakan desain "terbersih". Namun # 2 adalah perbedaan yang halus, tetapi penting antara pemahaman saya tentang istilah "antarmuka" dan fitur bahasa C ++. Sebagai hasil dari ini saya saat ini (hampir) tidak pernah menyebut hal-hal sebagai "antarmuka" di C ++ dan berbicara dalam hal kelas dasar dan ukurannya. Saya akan mengatakan bahwa dalam konteks C ++ "antarmuka" adalah nama yang salah.

Namun telah menjadi perhatian saya bahwa tidak banyak orang membuat perbedaan seperti itu.

  1. Apakah saya akan kehilangan apa pun dengan membiarkan (mis. protected) virtualFungsi tidak ada dalam "antarmuka" di C ++? (Perasaan saya justru sebaliknya - lokasi yang lebih alami untuk kode bersama)
  2. Apakah istilah "antarmuka" bermakna dalam C ++ - apakah itu menyiratkan hanya murni virtualatau apakah adil untuk memanggil kelas C ++ tanpa variabel anggota antarmuka masih?
Flexo
sumber
C # memiliki beberapa pewarisan antarmuka yang sama, pewarisan tunggal implementasi seperti Java, tetapi melalui penggunaan metode ekstensi generik , internalHelperThingdapat disimulasikan dalam hampir semua kasus.
Sjoerd
Perhatikan bahwa mengekspos anggota virtual adalah praktik yang buruk.
Klaim
publik ~Foo() {}dalam kelas abstrak adalah kesalahan di bawah (hampir) setiap keadaan.
Wolf

Jawaban:

11

Dalam C ++, istilah "antarmuka" tidak hanya memiliki satu definisi yang diterima secara luas - jadi setiap kali Anda akan menggunakannya, Anda harus mengatakan apa yang Anda maksudkan secara tepat - kelas dasar virtual dengan atau tanpa implementasi default, file header, anggota publik dari kelas yang sewenang-wenang dan sebagainya.

Mengenai contoh Anda: di Jawa (dan serupa di C #), kode Anda mungkin akan menyiratkan pemisahan kekhawatiran:

interface IFoo {/*  ... */} // here is your interface

class FooBase implements IFoo 
{
     // make default implementations for interface methods
}

class Foo extends FooBase
{
}

Di C ++, Anda bisa melakukan ini tetapi Anda tidak dipaksa. Dan jika Anda suka memanggil kelas sebagai antarmuka jika tidak memiliki variabel anggota, tetapi berisi implementasi standar untuk beberapa metode, lakukan saja, tetapi pastikan semua orang yang Anda ajak bicara tahu apa yang Anda maksud.

Doc Brown
sumber
3
Makna lain yang mungkin dari "antarmuka", bagi seorang programmer C ++, adalah publicbagian - bagian dari .hfile.
David Thornley
Bahasa apa yang menjadi contoh kode Anda? Jika itu adalah upaya untuk menjadi C ++ saya akan menangis ...
Qix - MONICA DISALAHKAN
@Qix: tetap mudah, baca kembali postingan saya (ini menyatakan dengan jelas bahwa kode tersebut ada di Jawa), dan jika Anda mendapat> 2000 poin, Anda dapat mengedit posting saya untuk menambahkan kode contoh C ++ yang setara, untuk membuat contoh saya lebih bersih.
Doc Brown
Jika itu java maka itu masih salah, dan tidak, saya tidak bisa mengeditnya; tidak cukup karakter untuk diubah. Java tidak menggunakan titik dua dalam mengimplementasikan / memperluas, itulah sebabnya saya bertanya-tanya apakah ini merupakan upaya di C ++ ...
Qix - MONICA DISEBUTKAN
@Qix: jika Anda menemukan lebih banyak masalah sintaksis, Anda dapat menyimpannya sebagai hadiah natal :-)
Doc Brown
7

Kedengarannya agak seperti Anda mungkin telah jatuh ke dalam perangkap yang membingungkan makna apa itu bahwa antarmuka secara konseptual baik sebagai implementasi (antarmuka - huruf kecil 'i'), dan abstraksi (Antarmuka - huruf besar 'I' ).

Sehubungan dengan contoh Anda, bit kode pertama Anda hanyalah sebuah kelas. Sementara kelas Anda memiliki antarmuka dalam arti bahwa ia menyediakan metode untuk memungkinkan akses ke perilaku itu, itu bukan Antarmuka dalam arti deklarasi Antarmuka yang menyediakan lapisan abstraksi yang mewakili jenis perilaku yang Anda mungkin ingin kelas untuk melaksanakan. Jawaban Doc Brown untuk posting Anda menunjukkan dengan tepat apa yang saya bicarakan di sini.

Antarmuka sering disebut-sebut sebagai "solusi" untuk bahasa yang tidak mendukung banyak pewarisan, tetapi saya merasa itu lebih merupakan kesalahpahaman daripada kebenaran yang sulit (dan saya curiga saya mungkin akan disalahkan karena menyatakan itu!). Antarmuka benar-benar tidak ada hubungannya dengan pewarisan berganda karena mereka tidak memerlukan pewarisan, atau menjadi leluhur untuk memberikan kompatibilitas fungsional antar kelas, atau abstraksi implementasi untuk kelas. Bahkan, mereka dapat memungkinkan Anda untuk menghapus warisan secara efektif seandainya Anda ingin menerapkan kode Anda dengan cara itu - bukan berarti saya sepenuhnya merekomendasikannya, tetapi saya hanya mengatakan Anda bisalakukan. Jadi kenyataannya adalah bahwa terlepas dari hal-hal warisan, Antarmuka menyediakan sarana dengan mana kelas-jenis dapat didefinisikan, menetapkan aturan yang menentukan bagaimana objek dapat berkomunikasi satu sama lain, sehingga mereka memungkinkan Anda untuk menentukan perilaku yang harus didukung oleh kelas Anda tanpa mendikte metode khusus yang digunakan untuk menerapkan perilaku itu.

Apakah saya akan kehilangan sesuatu dengan membiarkan (mis. Dilindungi) fungsi non-virtual ada dalam "antarmuka" di C ++? (Perasaan saya justru sebaliknya - lokasi yang lebih alami untuk kode bersama)

Antarmuka murni dimaksudkan untuk sepenuhnya abstrak, karena memungkinkan definisi kontrak kompatibilitas antara kelas yang mungkin tidak selalu mewarisi perilaku mereka dari leluhur yang sama. Saat diimplementasikan, Anda ingin membuat pilihan tentang apakah akan memungkinkan perilaku implementasi diperluas dalam subkelas. Jika metode Anda tidak virtual, Anda kehilangan kemampuan untuk memperluas perilaku itu nanti jika Anda memutuskan Anda perlu membuat kelas turunan. Terlepas dari apakah implementasinya virtual atau tidak, Interface mendefinisikan kompatibilitas perilaku di dalam dan dari dirinya sendiri, sementara kelas menyediakan implementasi perilaku itu untuk instance yang diwakili oleh kelas.

Apakah istilah "antarmuka" bermakna dalam C ++ - apakah itu hanya menyiratkan virtual murni atau apakah adil untuk memanggil kelas C ++ tanpa variabel anggota antarmuka?

Maafkan saya, tapi sudah lama sejak saya benar-benar menulis aplikasi serius di C ++. Saya ingat bahwa Interface adalah kata kunci untuk keperluan abstraksi seperti yang saya jelaskan di sini. Saya tidak akan menyebut kelas C ++ dalam bentuk apa pun sebagai Antarmuka, saya lebih suka mengatakan bahwa kelas memiliki antarmuka dalam arti yang telah saya uraikan di atas. Dalam arti istilah IS bermakna, tetapi itu benar-benar tergantung pada konteksnya.

S.Robins
sumber
1
+1 untuk perbedaan antara "memiliki" dan "menjadi" sebuah antarmuka.
Doc Brown
6

Antarmuka Java bukanlah "solusi", mereka adalah keputusan desain yang disengaja untuk menghindari beberapa masalah dengan banyak warisan seperti warisan berlian, dan untuk mendorong praktik desain yang meminimalkan penggandengan.

Antarmuka Anda dengan metode yang dilindungi adalah kasus buku teks yang perlu "lebih suka komposisi daripada warisan." Mengutip dari Sutter dan Alexandrescu di bagian dengan nama itu dari Standar C ++ C ++ mereka yang sangat baik :

Hindari pajak warisan: Warisan adalah hubungan kopling paling ketat kedua di C ++, yang kedua setelah persahabatan. Kopling ketat tidak diinginkan dan harus dihindari jika memungkinkan. Karena itu, lebih suka komposisi daripada warisan kecuali Anda tahu bahwa yang terakhir benar-benar menguntungkan desain Anda.

Dengan memasukkan fungsi pembantu Anda di antarmuka Anda, Anda mungkin menyimpan sedikit pengetikan sekarang, tetapi Anda memperkenalkan kopling yang akan melukai Anda di ujung jalan. Ini hampir selalu lebih baik untuk jangka panjang untuk membuat fungsi pembantu Anda terpisah dan dilewatkan Foo*.

STL adalah contoh yang cukup bagus dari ini. Fungsi pembantu sebanyak mungkin ditarik keluar <algorithm>alih-alih berada di kelas wadah. Misalnya, karena sort()menggunakan API penampung publik untuk melakukan tugasnya, Anda tahu Anda dapat menerapkan algoritma penyortiran Anda sendiri tanpa harus mengubah kode STL. Desain ini memungkinkan perpustakaan seperti boost, yang meningkatkan STL alih-alih menggantinya, tanpa STL perlu tahu apa-apa tentang boost.

Karl Bielefeldt
sumber
2

Apakah saya akan kehilangan sesuatu dengan membiarkan (mis. Dilindungi) fungsi non-virtual ada dalam "antarmuka" di C ++? (Perasaan saya justru sebaliknya - lokasi yang lebih alami untuk kode bersama)

Anda akan berpikir begitu, tetapi menempatkan metode non-virtual yang dilindungi dalam kelas yang abstrak menentukan implementasi bagi siapa saja yang menulis subkelas. Melakukan hal itu mengalahkan tujuan antarmuka dalam arti murni, yaitu untuk menyediakan lapisan yang menyembunyikan apa yang ada di bawahnya.

Ini adalah salah satu contoh di mana tidak ada jawaban satu ukuran untuk semua dan Anda harus menggunakan pengalaman dan penilaian Anda untuk membuat keputusan. Jika Anda dapat mengatakan dengan keyakinan 100% bahwa setiap subkelas yang mungkin dari kelas Anda yang sepenuhnya virtual Fooakan selalu membutuhkan implementasi metode yang dilindungi bar(), maka itu Fooadalah tempat yang tepat untuk itu. Setelah Anda memiliki subkelas Bazyang tidak perlu bar(), Anda juga harus hidup dengan fakta bahwa tidak Bazakan memiliki akses ke kode yang seharusnya atau melalui latihan menata ulang hierarki kelas Anda. Yang pertama bukan latihan yang baik dan yang terakhir bisa memakan waktu lebih dari beberapa menit yang diperlukan untuk mengatur hal-hal dengan benar.

Apakah istilah "antarmuka" bermakna dalam C ++ - apakah itu hanya menyiratkan virtual murni atau apakah adil untuk memanggil kelas C ++ tanpa variabel anggota antarmuka?

Bagian 10.4 dari standar C ++ menyebutkan secara singkat penggunaan kelas abstrak untuk mengimplementasikan antarmuka tetapi tidak mendefinisikannya secara formal. Istilah ini bermakna dalam konteks ilmu komputer umum, dan siapa pun yang kompeten harus memahami bahwa " Fooadalah antarmuka untuk (apa pun)" menyiratkan beberapa bentuk abstraksi. Orang-orang dengan paparan bahasa dengan konstruksi antarmuka yang didefinisikan mungkin berpikir virtual murni, tetapi siapa pun yang perlu benar-benar bekerja dengannya Fooakan melihat definisi sebelum melanjutkan.

Blrfl
sumber
2
Apa perbedaan antara menyediakan deklarasi virtual murni dan deklarasi plus implementasi? Dalam kedua kasus, harus ada implementasi, dan bazselalu dapat memiliki implementasi seperti return false;atau apa pun. Jika metode ini tidak berlaku untuk semua subclass, itu tidak termasuk dalam kelas abstrak dasar dalam bentuk apa pun.
David Thornley
1
Saya pikir kalimat terakhir Anda mengatakan kita berada di halaman yang sama: tidak ada alasan Anda tidak bisa memiliki metode yang dilindungi dalam kelas abstrak, tetapi mereka tidak boleh lebih tinggi di pohon warisan daripada yang diperlukan. Saya hanya berpikir bahwa jika suatu kelas harus mengelabui implementasi, itu tidak di tempat yang tepat di pohon.
Blrfl