Bagaimana cara mengatur kelas yang mewakili antarmuka? Apakah ini hanya kelas dasar abstrak?
c++
inheritance
interface
abstract-class
pure-virtual
Aaron Fischer
sumber
sumber
Jawaban:
Untuk memperluas jawaban oleh bradtgmurray , Anda mungkin ingin membuat satu pengecualian untuk daftar metode virtual murni antarmuka Anda dengan menambahkan destruktor virtual. Ini memungkinkan Anda untuk meneruskan kepemilikan pointer ke pihak lain tanpa memaparkan kelas turunannya. Destructor tidak perlu melakukan apa-apa, karena antarmuka tidak memiliki anggota konkret. Mungkin tampak kontradiktif untuk mendefinisikan fungsi sebagai virtual dan inline, tetapi percayalah - tidak.
Anda tidak harus menyertakan body untuk destruktor virtual - ternyata beberapa kompiler mengalami kesulitan dalam mengoptimalkan destruktor kosong dan Anda lebih baik menggunakan default.
sumber
=0
destructor virtual ( ) murni dengan sebuah body. Keuntungannya di sini adalah bahwa kompiler dapat, secara teoritis, melihat bahwa vtable tidak memiliki anggota yang valid sekarang, dan membuangnya sama sekali. Dengan destruktor virtual dengan tubuh, kata destruktor dapat disebut (hampir) misalnya di tengah konstruksi melaluithis
pointer (ketika objek yang dibangun masihParent
bertipe), dan oleh karena itu kompiler harus menyediakan vtable yang valid. Jadi, jika Anda tidak secara eksplisit memanggil destruktor virtual melaluithis
selama konstruksi :) Anda dapat menghemat ukuran kode.override
kata kunci untuk memungkinkan argumen waktu kompilasi dan memeriksa jenis nilai kembali. Misalnya, dalam deklarasi Childvirtual void OverrideMe() override;
Buat kelas dengan metode virtual murni. Gunakan antarmuka dengan membuat kelas lain yang menimpa metode virtual tersebut.
Metode virtual murni adalah metode kelas yang didefinisikan sebagai virtual dan ditugaskan ke 0.
sumber
override
di C ++ 11Seluruh alasan Anda memiliki kategori-jenis Antarmuka khusus selain kelas dasar abstrak di C # / Java adalah karena C # / Java tidak mendukung pewarisan berganda.
C ++ mendukung multiple inheritance, jadi tipe khusus tidak diperlukan. Kelas dasar abstrak tanpa metode non-abstrak (virtual murni) secara fungsional setara dengan antarmuka C # / Java.
sumber
Thread
contoh. Warisan berganda dapat berupa desain dan komposisi yang buruk. Itu semua tergantung kasus.Tidak ada konsep "antarmuka" per se dalam C ++. AFAIK, antarmuka pertama kali diperkenalkan di Jawa untuk mengatasi kurangnya pewarisan berganda. Konsep ini ternyata sangat berguna, dan efek yang sama dapat dicapai dalam C ++ dengan menggunakan kelas dasar abstrak.
Kelas dasar abstrak adalah kelas di mana setidaknya satu fungsi anggota (metode dalam istilah Java) adalah fungsi virtual murni yang dideklarasikan menggunakan sintaksis berikut:
Kelas dasar abstrak tidak dapat dipakai, yaitu Anda tidak bisa mendeklarasikan objek kelas A. Anda hanya bisa mendapatkan kelas dari A, tetapi setiap kelas turunan yang tidak menyediakan implementasi
foo()
juga akan abstrak. Untuk berhenti menjadi abstrak, kelas turunan harus menyediakan implementasi untuk semua fungsi virtual murni yang diwarisi.Perhatikan bahwa kelas dasar abstrak bisa lebih dari satu antarmuka, karena dapat berisi data anggota dan fungsi anggota yang bukan virtual murni. Setara dengan antarmuka akan menjadi kelas dasar abstrak tanpa data dengan hanya fungsi virtual murni.
Dan, seperti yang ditunjukkan oleh Mark Ransom, kelas dasar abstrak harus menyediakan destruktor virtual, sama seperti kelas dasar mana pun.
sumber
Sejauh yang saya bisa uji, sangat penting untuk menambahkan destructor virtual. Saya menggunakan objek yang dibuat dengan
new
dan dihancurkandelete
.Jika Anda tidak menambahkan destruktor virtual di antarmuka, maka destruktor dari kelas yang diturunkan tidak disebut.
Jika Anda menjalankan kode sebelumnya tanpa
virtual ~IBase() {};
, Anda akan melihat bahwa destructorTester::~Tester()
tidak pernah dipanggil.sumber
Jawaban saya pada dasarnya sama dengan yang lain tapi saya pikir ada dua hal penting yang harus dilakukan:
Deklarasikan destruktor virtual di antarmuka Anda atau buat yang non-virtual yang dilindungi untuk menghindari perilaku yang tidak terdefinisi jika seseorang mencoba menghapus objek tipe
IDemo
.Gunakan pewarisan virtual untuk menghindari masalah dengan pewarisan berganda. (Ada lebih sering pewarisan berganda ketika kita menggunakan antarmuka.)
Dan seperti jawaban lain:
Gunakan antarmuka dengan membuat kelas lain yang menimpa metode virtual tersebut.
Atau
Dan
sumber
Di C ++ 11 Anda dapat dengan mudah menghindari warisan sama sekali:
Dalam hal ini, suatu Antarmuka memiliki semantik referensi, yaitu Anda harus memastikan bahwa objek hidup lebih lama dari antarmuka (juga dimungkinkan untuk membuat antarmuka dengan semantik nilai).
Jenis antarmuka ini memiliki pro dan kontra:
Akhirnya, warisan adalah akar dari semua kejahatan dalam desain perangkat lunak yang kompleks. Dalam Semantics Value Seant Parent dan Polymorphism berbasis Konsep (sangat disarankan, versi yang lebih baik dari teknik ini dijelaskan di sana) kasus berikut dipelajari:
Katakanlah saya memiliki aplikasi tempat saya menangani bentuk saya secara polimorfik menggunakan
MyShape
antarmuka:Dalam aplikasi Anda, Anda melakukan hal yang sama dengan berbagai bentuk menggunakan
YourShape
antarmuka:Sekarang katakan Anda ingin menggunakan beberapa bentuk yang saya kembangkan di aplikasi Anda. Secara konseptual, bentuk kami memiliki antarmuka yang sama, tetapi untuk membuat bentuk saya berfungsi di aplikasi Anda, Anda perlu memperluas bentuk saya sebagai berikut:
Pertama, memodifikasi bentuk saya mungkin tidak mungkin sama sekali. Lebih lanjut, multiple inheritance menuntun jalan ke kode spaghetti (bayangkan proyek ketiga datang menggunakan
TheirShape
antarmuka ... apa yang terjadi jika mereka juga memanggil fungsi draw merekamy_draw
?).Pembaruan: Ada beberapa referensi baru tentang polimorfisme berbasis non-warisan:
sumber
Circle
kelas adalah desain yang buruk. Anda harus menggunakanAdapter
pola dalam kasus seperti itu. Maaf jika ini akan terdengar agak keras, tetapi cobalah untuk menggunakan perpustakaan kehidupan nyata sepertiQt
sebelum membuat penilaian tentang warisan. Warisan membuat hidup lebih mudah.Adapter
pola? Saya tertarik melihat kelebihannya.Square
belum ada di sana? Ramalan? Karena itulah ia terlepas dari kenyataan. Dan pada kenyataannya jika Anda memilih untuk mengandalkan pustaka "MyShape" Anda dapat mengadopsi antarmuka-nya dari awal. Dalam contoh bentuk ada banyak nonsense (salah satunya adalah Anda memiliki duaCircle
struct), tetapi adaptor akan terlihat seperti itu -> ideone.com/UogjWkSemua jawaban bagus di atas. Satu hal tambahan yang harus Anda ingat - Anda juga dapat memiliki destruktor virtual murni. Satu-satunya perbedaan adalah Anda masih perlu mengimplementasikannya.
Bingung?
Alasan utama Anda ingin melakukan ini adalah jika Anda ingin memberikan metode antarmuka, seperti yang saya miliki, tetapi menjadikannya overriding sebagai opsional.
Untuk membuat kelas sebagai kelas antarmuka membutuhkan metode virtual murni, tetapi semua metode virtual Anda memiliki implementasi standar, jadi satu-satunya metode yang tersisa untuk membuat virtual murni adalah destruktor.
Mengimplementasikan kembali destruktor di kelas turunan bukanlah masalah besar sama sekali - saya selalu menerapkan kembali destruktor, virtual atau tidak, di kelas turunan saya.
sumber
Jika Anda menggunakan kompiler C ++ Microsoft, maka Anda dapat melakukan hal berikut:
Saya suka pendekatan ini karena menghasilkan banyak kode antarmuka yang lebih kecil dan ukuran kode yang dihasilkan bisa jauh lebih kecil. Penggunaan novtable menghapus semua referensi ke pointer vtable di kelas itu, sehingga Anda tidak akan pernah bisa instantiate secara langsung. Lihat dokumentasi di sini - novtable .
sumber
novtable
standarvirtual void Bar() = 0;
= 0;
yang saya tambahkan). Baca dokumentasi jika Anda tidak memahaminya.= 0;
dan menganggap itu hanya cara non-standar untuk melakukan hal yang persis sama.Sedikit tambahan untuk apa yang ditulis di sana:
Pertama, pastikan destructor Anda juga virtual murni
Kedua, Anda mungkin ingin mewarisi secara virtual (bukan normal) ketika Anda menerapkan, hanya untuk langkah-langkah yang baik.
sumber
Anda juga dapat mempertimbangkan kelas kontrak yang diimplementasikan dengan NVI (Non Virtual Interface Pattern). Contohnya:
sumber
Saya masih baru dalam pengembangan C ++. Saya mulai dengan Visual Studio (VS).
Namun, tampaknya tidak ada yang disebutkan
__interface
dalam VS (.NET) . Saya tidak begitu yakin apakah ini cara yang baik untuk mendeklarasikan antarmuka. Tetapi tampaknya memberikan penegakan tambahan (disebutkan dalam dokumen ). Sedemikian rupa sehingga Anda tidak perlu menentukan secara eksplisitvirtual TYPE Method() = 0;
, karena itu akan secara otomatis dikonversi.Jika ada yang punya sesuatu yang menarik tentang itu, silakan bagikan. :-)
Terima kasih.
sumber
Memang benar itu
virtual
adalah standar de-facto untuk mendefinisikan antarmuka, jangan lupa tentang pola klasik C-like, yang dilengkapi dengan konstruktor di C ++:Ini memiliki keuntungan bahwa Anda dapat mengikat kembali runtime acara tanpa harus membangun kelas Anda lagi (karena C ++ tidak memiliki sintaks untuk mengubah tipe polimorfik, ini merupakan solusi untuk kelas bunglon).
Kiat:
click
konstruktor keturunan Anda.protected
anggota dan memilikipublic
referensi dan / atau pengambil.if
perubahan s vs status dalam kode Anda, ini mungkin lebih cepat daripadaswitch()
es atauif
s (turnaround diharapkan sekitar 3-4if
s, tetapi selalu ukur terlebih dahulu.std::function<>
lebih dari pointer fungsi, Anda mungkin dapat mengelola semua data objek Anda di dalamnyaIBase
. Dari titik ini, Anda dapat memiliki skema nilai untukIBase
(misalnya,std::vector<IBase>
akan berfungsi). Perhatikan bahwa ini mungkin lebih lambat tergantung pada kompiler dan kode STL Anda; juga bahwa implementasi saat inistd::function<>
cenderung memiliki overhead jika dibandingkan dengan pointer fungsi atau bahkan fungsi virtual (ini mungkin berubah di masa depan).sumber
Berikut adalah definisi
abstract class
dalam standar c ++n4687
13.4.2
sumber
Hasil: Area segi empat: 35 Area segitiga: 17
Kami telah melihat bagaimana kelas abstrak mendefinisikan antarmuka dalam hal getArea () dan dua kelas lainnya menerapkan fungsi yang sama tetapi dengan algoritma yang berbeda untuk menghitung area khusus untuk bentuk.
sumber