C++, warisan virtual, kelas abstrak aneh + masalah klon

Maaf untuk jumlah kode sumber yang lebih besar. Ada tiga kelas abstrak P, L, PL. Kelas PL ketiga diturunkan dari kelas P dan L menggunakan warisan virtual:

template <typename T>  //Abstract class
class P
{

    public:
            virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }

template <typename T>  //Abstract class
class L
{
    public:
            virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}

template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0;
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}

Setiap kelas memiliki implementasi metode kloningnya sendiri.

Dua kelas berikutnya PA, PC diturunkan dari kelas P menggunakan pewarisan virtual:

template <typename T>
class PC : virtual public P <T>
{
    public:
            PC() : P <T> () {}
            virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
            virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

Dua kelas terakhir PCL dan PAL dikelola menggunakan warisan virtual dari PC dan PL, PA dan PL.

template <typename T>
class PCL : public PC <T>, public PL <T>
{
    public:
            PCL() : P <T> (), PC <T> (), PL <T> ()  {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this );}
};

template <typename T>
class PAL : public PA <T>, public PL <T>
{
    public:
            PAL() : P <T> (), PA <T> (), PL <T> () {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}

};

Ada diagram dependensi kelas:

.......... P .... L.....
........../|\..../......
........./.|.\../.......
......../..|..\/........
.......PC..PA..PL.......
.......|...|.../|.......
.......|...|../.|.......
.......|...PAL..|.......
.......|........|.......
.......PCL_____/........  

Tolong, jangan membahas proposal ini :-))). Saya punya 3 pertanyaan berikut:

1) Apakah ketergantungan kelas ini ditulis ulang dalam C++ dengan benar (pertama-tama penempatan "virtual")?

2) Saya tidak yakin apa yang salah, silakan lihat kodenya:

int main(int argc, _TCHAR* argv[])
{
PCL <double> * pcl = new PCL <double>(); //Object of abstract class not allowed
PAL <double> * pal = new PAL <double>(); //Object of abstract class not allowed
PL <double> *pl1 = pcl->clone(); //Polymorphism
PL <double> *pl2 = pal->clone(); //Polymorphism
return 0;
} 

Tidak mungkin membuat objek baru dari kelas PAL/PCL, kedua kelas ditandai sebagai abstrak. Tapi mereka tidak abstrak. Dimana masalahnya?

3) Apakah mungkin menggunakan polimorfisme bersama dengan metode clone()? Silakan lihat kode di atas...

Terima kasih atas bantuan Anda...


PERTANYAAN DIPERBARUI

Saya memperbaiki kodenya. Namun muncul error berikut saat menggunakan compiler VS 2010:

template <typename T>  //Abstract class
class P
{
    public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }


template <typename T>  //Abstract class
class L
{
    public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0; 
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}


template <typename T>
class PC : virtual public P <T>
{
    protected:
            T pc;
    public:
            PC() : P <T> () {}
    virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
    virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
            PCL() : P <T> (), PC <T> (), PL <T> () {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
}; //Error using VS 2010: Error 1   error C2250: 'PCL<T>' : ambiguous inheritance of 'PC<T> *P<T>::clone(void) const'


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
            PAL() : P <T> (), PA <T> (), PL <T> ()  {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
}; //Error VS 2010: Error   1   error C2250: 'PAL<T>' : ambiguous inheritance of 'PA<T> *P<T>::clone(void) const'


int main(int argc, char* argv[])
{
PCL <double> * pcl = new PCL <double>();
PAL <double> * pal = new PAL <double>();
PL <double> *pl1 = pcl->clone();
PL <double> *pl2 = pal->clone();
return 0;
}

Mungkin saya melewatkan sesuatu... Tapi g++ mengkompilasi kode ini dengan OK.


person Johnas    schedule 03.08.2011    source sumber
comment
Apakah destruktor virtual murni Anda di P, L dan PL memiliki implementasi default?   -  person Chad    schedule 03.08.2011
comment
Saya rasa Anda tidak dapat menambahkan definisi ke deklarasi fungsi murni; ucapkan = 0;. Juga ada tanda kurung yang hilang setelah PL <T> { } (seharusnya PL <T>() { }). Seperti yang dikatakan @Chad, Anda tidak perlu membuat destruktor menjadi virtual murni jika Anda sudah memiliki fungsi virtual murni lainnya.   -  person Kerrek SB    schedule 03.08.2011
comment
@Chad: Ya, saya mengoreksi kodenya...   -  person Johnas    schedule 03.08.2011


Jawaban (5)


Ini dikompilasi untuk saya menggunakan VC10:

template <typename T>  //Abstract class
class P
{
public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}


template <typename T>  //Abstract class
class L
{
public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
public:
    PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
//  virtual PL <T> *clone() const  = 0; // REMOVED!
};

template <typename T>
PL<T>::~PL() {}


template <typename T>
class PC : virtual public P <T>
{
protected:
    T pc;
public:
    PC() : P <T> () {}
    virtual ~PC() {}
    virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
public:
    PA() : P <T> () {}
    virtual ~PA() {}
    virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
    PCL() : P <T> (), PC <T> (), PL <T> () {}
    virtual ~PCL() {}
    virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
};


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
    PAL() : P <T> (), PA <T> (), PL <T> ()  {}
    virtual ~PAL() {}
    virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
};


int main()
{
    PCL <double> * pcl = new PCL <double>();
    PAL <double> * pal = new PAL <double>();
    PL <double> *pl1 = pcl->clone();
    PL <double> *pl2 = pal->clone();
    return 0;
}

Perhatikan bahwa saya harus menghapus PL <T> *PL <T>::clone() agar VC menerima kode tersebut. Masalahnya adalah, jika Anda memiliki PL<T>, dan menyebutnya clone(), maka secara statis akan mengembalikan L<T>*, bukan PL<T>*, meskipun tipe dinamisnya adalah kelas yang diturunkan dari PL<T>.

person sbi    schedule 03.08.2011
comment
Oke, itu berhasil. Namun Anda menghapus semua definisi metode kloning di tiga kelas abstrak. - person Johnas; 03.08.2011
comment
@Johnas: Metode ini mencoba membuat instance objek dari kelas abstrak miliknya sendiri. Itu pasti tidak akan berhasil. Namun Anda tetap tidak memerlukannya, karena Anda tidak akan pernah memiliki objek kelas ini (karena bersifat abstrak). Anda akan selalu hanya memiliki objek kelas turunan, dan objek tersebut mengimplementasikan clone(). Sungguh, satu-satunya masalah dengan kode dari jawaban saya adalah seperti yang saya tulis dalam jawaban saya di bawah kode. - person sbi; 03.08.2011
comment
Saya lupa, bahwa saya dapat membuat pointer ke kelas abstrak namun bukan objeknya (atau secara dinamis)... Terima kasih atas bantuan Anda... - person Johnas; 03.08.2011

Hanya beberapa kesalahan kecil dalam kode Anda:

  • Perbaiki penginisialisasi dasar: PAL() : PC <T>(), PL <T>() {} Tidak ada inisialisasi P<T>, yang bukan merupakan basis langsung; tapi[Maaf, itu salah -- Anda harus memanggil konstruktor basis virtual karena pewarisan virtual.] Anda harus mengucapkan tanda kurung bulat.

  • Deklarasikan fungsi virtual murni tanpa definisi: virtual L <T> *clone() const = 0;

  • Periksa kembali apakah Anda ingin PL mewarisi secara virtual dari P tetapi tidak secara virtual dari L (tapi tidak apa-apa).

  • Periksa kembali apakah semua clone() Anda memiliki keteguhan yang sama.

  • Jika Anda sudah memiliki satu fungsi virtual murni di kelas dasar abstrak Anda, Anda tidak boleh menjadikan destruktornya murni. Sebaliknya, ucapkan, virtual ~T() { }. Jika tidak, Anda tetap harus memberikan definisi destruktor virtual murni (karena destruktor selalu harus dapat dipanggil): virtual T::~T() { }

person Kerrek SB    schedule 03.08.2011
comment
Memang kesalahan murni harus berasal dari clone() bukan const. ideone.com/3bTty adalah versi kode yang sudah dibersihkan, dan akan dikompilasi (meskipun bukan tautan). Karena kode tersebut memiliki banyak kesalahan, patut dipertanyakan apakah itu benar-benar sesuai dengan kode yang bermasalah dengan OP. - person PlasmaHH; 03.08.2011
comment
Saya sebenarnya tidak mendapatkan peringatan atau kesalahan apa pun terkait keteguhan tersebut. Saya tidak 100% yakin saat ini, namun ada kemungkinan bahwa penyembunyian yang salah justru terjadi, bukannya override. Saya tidak ingin menghabiskan lebih banyak waktu dengan hal ini sekarang, tetapi sangat penting untuk mendapatkan tanda tangan yang benar saat melakukan override. - person Kerrek SB; 03.08.2011
comment
Saya tidak mengharapkan peringatan atau kesalahan apa pun mengenai kekonstanan juga, itu hanyalah tanda tangan lain, lihat saja seolah-olah itu adalah kesalahan ketik pada nama fungsi. - person PlasmaHH; 03.08.2011
comment
Jadi apa masalahnya? Saya dapat mengkompilasinya dengan baik, apakah ada pertanyaan lain? - person Kerrek SB; 03.08.2011
comment
Saya mendapat kesalahan berikut: Kesalahan 1 kesalahan C2250: 'PCL‹T›' : warisan ambigu 'PC‹T› *P‹T›::clone(void) const'... Silakan lihat kode yang diperbarui. .. ; - person Johnas; 03.08.2011
comment
Terima kasih atas kesabaran Anda. Tapi saya tidak melihat ada masalah lain, mungkin saya buta :-). Kode ini tidak dikompilasi menggunakan MSVS tetapi OK dengan g++.. - person Johnas; 03.08.2011
comment
Saya salah melakukan konstruksi basis virtual, Anda benar memiliki P<T>() di penginisialisasi. Saya tidak tahu, itu dikompilasi dengan baik di GCC dengan semua peringatan. Versi MSVS manakah yang Anda miliki? 2010SP1? - person Kerrek SB; 03.08.2011
comment
Saya menggunakan MSVS2010 sp1. Saya memposting versi final kode saya di pertanyaan yang diperbarui... Bisakah Anda memeriksanya? - person Johnas; 03.08.2011
comment
Maaf, saya hanya punya GCC! Mungkin orang lain di sini bisa melihatnya. - person Kerrek SB; 03.08.2011
comment
Hm, hanya karena penasaran, dan bukan karena harus, apa yang terjadi jika Anda membuat PAL dan PCL mewarisi secara virtual? - person Kerrek SB; 03.08.2011

MSVC tidak mendukung pengembalian kovarian dengan benar. Saat Anda mengira telah mengesampingkan fungsi tersebut, sebenarnya Anda baru saja menyembunyikannya. Itulah masalahnya.

person Puppy    schedule 03.08.2011
comment
@Johnas: Bisakah Anda menghapus PL <T> *PL <T>::clone()? Karena itu dikompilasi untuk saya. (Yaitu, ketika saya menghapus implementasi fungsi clone() virtual murni yang mencoba membuat instance kelas abstrak.) - person sbi; 03.08.2011
comment
Bisakah Anda memberi saya saran bagaimana mengubah kode agar dapat diterjemahkan menggunakan MSVS 2010? - person Johnas; 03.08.2011
comment
@Johnas: Seperti yang saya katakan, ini dikompilasi untuk saya jika saya menghapus PL <T> *PL <T>::clone(). - person sbi; 03.08.2011
comment
Saya mengirim postingan saya pada saat yang sama seperti Anda :-) Setelah menghapus PL‹T› * PL ‹T› :: clone() const { return new PL ‹T› ( *this );} tidak ada yang berubah, kesalahan yang sama ... - person Johnas; 03.08.2011
comment
@Johnas: Jangan hanya menghapus definisi fungsi anggota, tetapi juga deklarasi-nya. Ini dikompilasi untuk saya. - person sbi; 03.08.2011
comment
@Johnas: Lihat jawaban saya. - person sbi; 03.08.2011
comment
Maaf, saya tidak mengerti maksud Anda... PL ‹T› *PL ‹T›::clone() adalah bagian dari definisi, deklarasinya adalah virtual PL ‹T› *clone() const = 0 ... - person Johnas; 03.08.2011

Karena metode clone di kelas P Anda bersifat abstrak seperti yang didefinisikan

virtual P <T> *clone() const = 0 {};

(tapi {} itu salah). Apa yang harus Anda pahami adalah bahwa setelah templat ini dipakai, ini adalah metode terpisah dengan tanda tangan yang sama sekali berbeda dari metode kloning kelas turunan. pembuatan contoh template berperilaku seolah-olah menghasilkan kode baru. Jadi ini adalah metode abstrak murni yang tidak pernah diterapkan. Sehingga menjadikan setiap orang yang mewarisi metode ini (setiap kelas turunan) menjadi kelas abstrak.


Sunting: Adapun pertanyaan ketiga. Polimorfisme waktu berjalan dan waktu kompilasi tidak tercampur dengan baik. Saya tidak tahu mengapa Anda ingin menggunakan struktur yang sedemikian rumit, tetapi saya yakin bahwa desainnya dapat jauh lebih disederhanakan.

person Osada Lakmal    schedule 03.08.2011
comment
Jadi ini adalah metode abstrak murni yang tidak pernah diimplementasikan kurang tepat, karena fungsi-fungsi tersebut bukan templat anggota, tipe pengembaliannya hanya bergantung pada parameter templat dari templat kelas, dan itu tidak masalah. Pengembalian kovarian dari implementasi kelas turunan juga baik-baik saja, meskipun sulit dikenali (yang merupakan salah satu dari banyak alasan seseorang harus memikirkan kembali desain tersebut) - person PlasmaHH; 03.08.2011

  1. Ada beberapa kesalahan ketik yang perlu diperbaiki dalam kode Anda, tetapi virtuals di kisi warisan sudah benar. (Jumlahnya minimum. Anda dapat menambahkan lebih banyak; dalam kasus yang jarang terjadi di mana kisi-kisi seperti itu terjadi, sering kali yang terbaik adalah membuat semua warisan menjadi virtual, untuk melindungi dari evolusi di masa depan.)

  2. Tidak ada yang salah dengan kode pengujian Anda. Setelah kesalahan ketik pada dokumen asli diperbaiki, ia akan dikompilasi dengan baik dengan g++.

  3. Itu mungkin, dan itu akan berhasil seperti yang Anda lakukan.

person James Kanze    schedule 03.08.2011