Bagaimana Anda mendeklarasikan antarmuka dalam C ++?

Jawaban:

686

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.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Anda tidak harus menyertakan body untuk destruktor virtual - ternyata beberapa kompiler mengalami kesulitan dalam mengoptimalkan destruktor kosong dan Anda lebih baik menggunakan default.

Mark tebusan
sumber
106
Desctuctor virtual ++! Ini sangat penting. Anda mungkin juga ingin menyertakan deklarasi virtual murni dari operator = dan menyalin definisi konstruktor untuk mencegah compiler menghasilkan otomatis untuk Anda.
xan
33
Alternatif untuk destruktor virtual adalah destruktor yang dilindungi. Ini menonaktifkan penghancuran polimorfik, yang mungkin lebih tepat dalam beberapa keadaan. Cari "Pedoman # 4" di gotw.ca/publications/mill18.htm .
Fred Larson
9
Satu opsi lain adalah mendefinisikan =0destructor 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 melalui thispointer (ketika objek yang dibangun masih Parentbertipe), dan oleh karena itu kompiler harus menyediakan vtable yang valid. Jadi, jika Anda tidak secara eksplisit memanggil destruktor virtual melalui thisselama konstruksi :) Anda dapat menghemat ukuran kode.
Pavel Minaev
51
Betapa khas jawaban C ++ bahwa jawaban teratas tidak langsung menjawab pertanyaan (meskipun kodenya sempurna), malah mengoptimalkan jawaban sederhana.
Tim
18
Jangan lupa bahwa dalam C ++ 11 Anda dapat menentukan overridekata kunci untuk memungkinkan argumen waktu kompilasi dan memeriksa jenis nilai kembali. Misalnya, dalam deklarasi Childvirtual void OverrideMe() override;
Sean
245

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.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};
bradtgmurray
sumber
29
Anda harus memiliki destruktor do nothing di IDemo sehingga didefinisikan perilaku yang harus dilakukan: IDemo * p = new Child; / * apa pun * / hapus p;
Evan Teran
11
Mengapa metode OverrideMe di kelas anak adalah virtual? Apakah itu perlu?
Cemre
9
@ Cemre - tidak itu tidak perlu, tapi tidak ada salahnya juga.
PowerApp101
11
Biasanya merupakan ide yang baik untuk menjaga kata kunci 'virtual' setiap kali meng-override metode virtual. Meskipun tidak diperlukan, ini dapat membuat kode lebih jelas - jika tidak, Anda tidak memiliki indikasi bahwa metode itu dapat digunakan secara polimorfik, atau bahkan ada di kelas dasar.
Kevin
27
@Kevin Kecuali dengan overridedi C ++ 11
keyser
146

Seluruh 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.

Joel Coehoorn
sumber
17
Masih bagus untuk dapat membuat antarmuka, untuk menyelamatkan kita dari mengetik begitu banyak (virtual, = 0, virtual destructor). Juga banyak pewarisan sepertinya ide yang sangat buruk bagi saya dan saya belum pernah melihatnya digunakan dalam praktik, tetapi antarmuka diperlukan sepanjang waktu. Buruknya C ++ comity tidak akan memperkenalkan antarmuka hanya karena saya menginginkannya.
Ha11owed
9
Ha11owed: Ini memiliki antarmuka. Mereka disebut kelas dengan metode virtual murni dan tanpa implementasi metode.
Miles Rout
6
@ doc: java.lang.Thread memiliki metode dan konstanta yang mungkin tidak ingin Anda miliki di objek Anda. Apa yang harus dilakukan oleh kompiler jika Anda memperluas dari Thread, dan kelas lain dengan metode publik checkAccess ()? Apakah Anda benar-benar lebih suka menggunakan basis pointer bernama kuat seperti di C ++? Ini seperti desain yang buruk, Anda biasanya memerlukan komposisi di mana Anda berpikir bahwa Anda memerlukan banyak pewarisan.
Ha11owed
4
@ Ha11ow sudah lama sekali jadi saya tidak ingat detailnya, tapi ada metode dan kontan yang ingin saya miliki di kelas saya dan yang lebih penting saya ingin objek kelas turunan saya menjadi Threadcontoh. Warisan berganda dapat berupa desain dan komposisi yang buruk. Itu semua tergantung kasus.
doc
2
@ Dave: Benarkah? Objective-C memiliki evaluasi waktu kompilasi, dan template?
Deduplikator
51

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:

class A
{
  virtual void foo() = 0;
};

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.

Dima
sumber
13
Lebih dari "kurangnya pewarisan berganda" Saya akan mengatakan, untuk menggantikan pewarisan berganda. Java dirancang seperti ini sejak awal karena multiple inheritance menciptakan lebih banyak masalah daripada apa yang dipecahkannya. Jawaban yang bagus
OscarRyz
11
Oscar, itu tergantung pada apakah Anda seorang programmer C ++ yang belajar Java atau sebaliknya. :) IMHO, jika digunakan dengan bijaksana, seperti hampir semua hal di C ++, multiple inheritance memecahkan masalah. Kelas dasar abstrak "antarmuka" adalah contoh penggunaan pewarisan berganda yang sangat bijaksana.
Dima
8
@OscarRyz Salah. MI hanya menciptakan masalah ketika disalahgunakan. Sebagian besar dugaan masalah dengan MI juga akan muncul dengan desain alternatif (tanpa MI). Ketika orang memiliki masalah dengan desain mereka dengan MI, itu adalah kesalahan MI; jika mereka memiliki masalah desain dengan SI, itu kesalahan mereka sendiri. "Berlian maut" (warisan berulang) adalah contoh utama. Bashing MI bukan kemunafikan murni, tetapi dekat.
curiousguy
4
Secara semantik, antarmuka berbeda dari kelas abstrak, jadi antarmuka Java bukan hanya solusi teknis. Pilihan antara mendefinisikan antarmuka atau kelas abstrak didorong oleh semantik, bukan pertimbangan teknis. Mari kita bayangkan beberapa antarmuka "HasEngine": itu adalah aspek, fitur, dan dapat diterapkan ke / diimplementasikan oleh tipe yang sangat berbeda (apakah kelas atau kelas abstrak), jadi kita akan mendefinisikan antarmuka untuk itu, bukan kelas abstrak.
Marek Stanley
2
@ MarsekStanley, Anda mungkin benar, tetapi saya berharap Anda memilih contoh yang lebih baik. Saya suka memikirkannya dalam hal mewarisi antarmuka vs mewarisi implementasi. Dalam C ++ Anda bisa mewarisi antarmuka dan implementasi secara bersamaan (warisan publik) atau Anda hanya dapat mewarisi implementasi (warisan pribadi). Di Jawa Anda memiliki opsi untuk mewarisi antarmuka saja, tanpa implementasi.
Dima
43

Sejauh yang saya bisa uji, sangat penting untuk menambahkan destructor virtual. Saya menggunakan objek yang dibuat dengan newdan dihancurkan delete.

Jika Anda tidak menambahkan destruktor virtual di antarmuka, maka destruktor dari kelas yang diturunkan tidak disebut.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

Jika Anda menjalankan kode sebelumnya tanpa virtual ~IBase() {};, Anda akan melihat bahwa destructor Tester::~Tester()tidak pernah dipanggil.

Carlos C Soto
sumber
3
Jawaban terbaik di halaman ini karena itu membuat titik dengan memberikan contoh yang praktis dan dapat dikompilasi. Bersulang!
Lumi
1
Testet :: ~ Tester () berjalan hanya ketika obj "Dideklarasikan dengan Tester".
Alessandro L.
Sebenarnya, destruktor string privatename akan dipanggil, dan di memori, hanya itu yang akan dialokasikan untuk itu. Sejauh menyangkut runtime, ketika semua anggota konkret dari sebuah kelas dihancurkan, demikian juga instance kelas. Saya mencoba percobaan serupa dengan kelas Line yang memiliki dua struct Point dan menemukan kedua struct dihancurkan (Ha!) Setelah panggilan hapus atau kembali dari fungsi yang mencakup. valgrind mengkonfirmasi 0 kebocoran.
Chris Reid
33

Jawaban saya pada dasarnya sama dengan yang lain tapi saya pikir ada dua hal penting yang harus dilakukan:

  1. 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.

  2. Gunakan pewarisan virtual untuk menghindari masalah dengan pewarisan berganda. (Ada lebih sering pewarisan berganda ketika kita menggunakan antarmuka.)

Dan seperti jawaban lain:

  • Buat kelas dengan metode virtual murni.
  • Gunakan antarmuka dengan membuat kelas lain yang menimpa metode virtual tersebut.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }

    Atau

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }

    Dan

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
Rexxar
sumber
2
tidak perlu pewarisan virtual karena Anda tidak memiliki anggota data dalam antarmuka.
Robocide
3
Warisan virtual juga penting untuk metode. Tanpa itu, Anda akan mengalami ambiguitas dengan OverrideMe (), bahkan jika salah satu dari 'instance' itu adalah virtual murni (hanya mencoba ini sendiri).
Knarf Navillus
5
@Avishay_ " tidak perlu untuk warisan virtual karena Anda tidak memiliki anggota data dalam antarmuka. "
curiousguy
Perhatikan bahwa warisan virtual mungkin tidak berfungsi pada beberapa versi gcc, karena versi 4.3.3 yang dikirimkan bersama WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu
-1 karena memiliki destruktor yang dilindungi non-virtual, maaf
Wolf
10

Di C ++ 11 Anda dapat dengan mudah menghindari warisan sama sekali:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

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 MyShapeantarmuka:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

Dalam aplikasi Anda, Anda melakukan hal yang sama dengan berbagai bentuk menggunakan YourShapeantarmuka:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

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:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

Pertama, memodifikasi bentuk saya mungkin tidak mungkin sama sekali. Lebih lanjut, multiple inheritance menuntun jalan ke kode spaghetti (bayangkan proyek ketiga datang menggunakan TheirShapeantarmuka ... apa yang terjadi jika mereka juga memanggil fungsi draw mereka my_draw?).

Pembaruan: Ada beberapa referensi baru tentang polimorfisme berbasis non-warisan:

gnzlbg
sumber
5
Warisan TBH jauh lebih jelas daripada benda C ++ 11, yang berpura-pura sebagai antarmuka, tetapi lebih merupakan perekat untuk mengikat beberapa desain yang tidak konsisten. Bentuk contoh terlepas dari kenyataan dan Circlekelas adalah desain yang buruk. Anda harus menggunakan Adapterpola dalam kasus seperti itu. Maaf jika ini akan terdengar agak keras, tetapi cobalah untuk menggunakan perpustakaan kehidupan nyata seperti Qtsebelum membuat penilaian tentang warisan. Warisan membuat hidup lebih mudah.
doc
2
Sama sekali tidak terdengar kasar. Bagaimana contoh bentuk terlepas dari kenyataan? Bisakah Anda memberikan contoh (mungkin pada ideone) memperbaiki Lingkaran menggunakan Adapterpola? Saya tertarik melihat kelebihannya.
gnzlbg
OK saya akan mencoba untuk masuk ke dalam kotak kecil ini. Pertama-tama, Anda biasanya memilih perpustakaan seperti "MyShape" sebelum Anda mulai menulis aplikasi Anda sendiri, untuk mengamankan pekerjaan Anda. Kalau tidak, bagaimana Anda bisa tahu Squarebelum 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 dua Circlestruct), tetapi adaptor akan terlihat seperti itu -> ideone.com/UogjWk
doc
2
Itu tidak terlepas dari kenyataan itu. Ketika perusahaan A membeli perusahaan B dan ingin mengintegrasikan basis kode perusahaan B ke dalam perusahaan A, Anda memiliki dua basis kode yang sepenuhnya independen. Bayangkan masing-masing memiliki hierarki Bentuk berbagai jenis. Anda tidak dapat menggabungkan mereka dengan mudah dengan warisan, dan menambahkan perusahaan C dan Anda memiliki kekacauan besar. Saya pikir Anda harus menonton pembicaraan ini: youtube.com/watch?v=0I0FD3N5cgM Jawaban saya lebih tua, tetapi Anda akan melihat kesamaannya. Anda tidak harus mengimplementasikan ulang semuanya setiap saat, Anda dapat memberikan implementasi di antarmuka, dan memilih fungsi anggota jika tersedia.
gnzlbg
1
Saya telah menonton sebagian video dan ini benar-benar salah. Saya tidak pernah menggunakan dynamic_cast kecuali untuk tujuan debugging. Pemain dinamis berarti ada yang salah dengan desain Anda dan desain dalam video ini salah dengan desain :). Guy bahkan menyebutkan Qt, tetapi bahkan di sini dia salah - QLayout tidak mewarisi dari QWidget atau sebaliknya!
doc
9

Semua 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?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

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.

Rodyland
sumber
4
Mengapa, oh mengapa, ada yang mau membuat dtor dalam hal ini virtual murni? Apa untungnya? Anda baru saja memaksakan sesuatu ke kelas turunan yang sepertinya tidak perlu mereka sertakan - dtor.
Johann Gerell
6
Memperbarui jawaban saya untuk menjawab pertanyaan Anda. Destructor virtual murni adalah cara yang valid untuk mencapai (satu-satunya cara untuk mencapai?) Kelas antarmuka di mana semua metode memiliki implementasi standar.
Rodyland
7

Jika Anda menggunakan kompiler C ++ Microsoft, maka Anda dapat melakukan hal berikut:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

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 .

Tandai Ingram
sumber
4
Saya tidak mengerti mengapa Anda menggunakan novtablestandarvirtual void Bar() = 0;
Flexo
2
Ini selain (Saya baru saja memperhatikan yang hilang = 0;yang saya tambahkan). Baca dokumentasi jika Anda tidak memahaminya.
Tandai Ingram
Saya membacanya tanpa = 0;dan menganggap itu hanya cara non-standar untuk melakukan hal yang persis sama.
Flexo
4

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.

Uri
sumber
Saya suka warisan virtual karena secara konseptual itu berarti hanya ada satu instance dari kelas warisan. Harus diakui, kelas di sini tidak memiliki persyaratan ruang sehingga mungkin berlebihan. Saya belum pernah melakukan MI di C ++ untuk sementara waktu, tetapi bukankah pewarisan nonvirtual akan menyulitkan upcasting?
Uri
Mengapa, oh mengapa, ada yang mau membuat dtor dalam hal ini virtual murni? Apa untungnya? Anda baru saja memaksakan sesuatu ke kelas turunan yang sepertinya tidak perlu mereka sertakan - dtor.
Johann Gerell
2
Jika ada situasi di mana objek akan dihancurkan melalui pointer ke antarmuka, Anda harus memastikan bahwa destructor itu virtual ...
Uri
Tidak ada yang salah dengan destruktor virtual murni. Itu tidak perlu, tetapi tidak ada yang salah dengan itu. Menerapkan destruktor di kelas turunan hampir tidak menjadi beban besar pada implementor kelas itu. Lihat jawaban saya di bawah untuk alasan Anda melakukan ini.
Rodyland
+1 untuk warisan virtual, karena dengan antarmuka, kemungkinan kelas akan mendapatkan antarmuka dari dua jalur atau lebih. Saya memilih destruktor yang dilindungi di antarmuka tho.
doc
4

Anda juga dapat mempertimbangkan kelas kontrak yang diimplementasikan dengan NVI (Non Virtual Interface Pattern). Contohnya:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};
Luc Hermitte
sumber
Untuk pembaca lain, artikel Dr Dobbs ini "Conversations: Virtually Yours" oleh Jim Hyslop dan Herb Sutter menguraikan sedikit lebih banyak tentang mengapa orang mungkin ingin menggunakan NVI.
user2067021
Dan juga artikel ini "Virtualitas" oleh Herb Sutter.
user2067021
1

Saya masih baru dalam pengembangan C ++. Saya mulai dengan Visual Studio (VS).

Namun, tampaknya tidak ada yang disebutkan __interfacedalam 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 eksplisit virtual TYPE Method() = 0;, karena itu akan secara otomatis dikonversi.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

Namun, saya tidak menggunakannya karena saya khawatir tentang kompatibilitas kompilasi lintas platform, karena hanya tersedia di .NET.

Jika ada yang punya sesuatu yang menarik tentang itu, silakan bagikan. :-)

Terima kasih.

Yeo
sumber
0

Memang benar itu virtualadalah standar de-facto untuk mendefinisikan antarmuka, jangan lupa tentang pola klasik C-like, yang dilengkapi dengan konstruktor di C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

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:

  • Anda mungkin mewarisi dari ini sebagai kelas dasar (baik virtual dan non-virtual diizinkan) dan mengisi clickkonstruktor keturunan Anda.
  • Anda mungkin memiliki penunjuk fungsi sebagai protectedanggota dan memiliki publicreferensi dan / atau pengambil.
  • Seperti disebutkan di atas, ini memungkinkan Anda untuk beralih implementasi di runtime. Jadi ini adalah cara untuk mengelola negara juga. Bergantung pada jumlah ifperubahan s vs status dalam kode Anda, ini mungkin lebih cepat daripada switch()es atau ifs (turnaround diharapkan sekitar 3-4 ifs, tetapi selalu ukur terlebih dahulu.
  • Jika Anda memilih std::function<>lebih dari pointer fungsi, Anda mungkin dapat mengelola semua data objek Anda di dalamnya IBase. Dari titik ini, Anda dapat memiliki skema nilai untuk IBase(misalnya, std::vector<IBase>akan berfungsi). Perhatikan bahwa ini mungkin lebih lambat tergantung pada kompiler dan kode STL Anda; juga bahwa implementasi saat ini std::function<>cenderung memiliki overhead jika dibandingkan dengan pointer fungsi atau bahkan fungsi virtual (ini mungkin berubah di masa depan).
lorro
sumber
0

Berikut adalah definisi abstract classdalam standar c ++

n4687

13.4.2

Kelas abstrak adalah kelas yang hanya dapat digunakan sebagai kelas dasar dari beberapa kelas lain; tidak ada objek dari kelas abstrak yang dapat dibuat kecuali sebagai sub-objek dari kelas yang diturunkan darinya. Kelas adalah abstrak jika memiliki setidaknya satu fungsi virtual murni.

陳 力
sumber
-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

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.

hims
sumber
5
Ini bukan apa yang dianggap sebagai antarmuka! Itu hanya kelas dasar abstrak dengan satu metode yang perlu diganti! Antarmuka biasanya adalah objek yang hanya berisi definisi metode - "kontrak" yang harus dipenuhi oleh kelas lain saat mereka mengimplementasikan antarmuka.
guitarflow