Pertanyaan Menyalin entitas turunan hanya menggunakan pointer kelas dasar, (tanpa pengujian menyeluruh!) - C ++


Diberikan kelas dasar yang diwarisi oleh kebanyakan kelas turunan, dan struktur program yang mengharuskan Anda mengelola ini melalui pointer kelas dasar ke setiap entitas. Apakah ada cara sederhana untuk menyalin seluruh objek yang diturunkan saat hanya penunjuk kelas dasar yang diketahui?

Melihat sekelilingnya akan tampak mungkin (jika sangat membosankan) untuk menggunakan dynamic_cast panggil untuk memeriksa apakah pointer dasar dapat dilemparkan sebagai kelas turunan yang sesuai, kemudian salin menggunakan konstruktor salinan kelas turunan. Namun ini tidak benar-benar solusi optimal sebagian karena penggunaan dynamic_cast yang berlebihan dan juga akan melihat sakit kepala total untuk mempertahankan dan memperpanjang.

Solusi lain yang lebih elegan yang saya temui adalah sebagai berikut:

class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}

Kemudian dengan menelepon getCopy() pada pointer Kelas dasar untuk setiap objek yang diturunkan masih mendapatkan pointer kelas dasar kembali tetapi juga seluruh objek yang diturunkan telah disalin. Metode ini terasa lebih mudah dipertahankan karena hanya membutuhkan yang serupa getCopy() berfungsi untuk berada di semua kelas turunan, dan jauh dengan kebutuhan untuk menguji terhadap semua objek turunan yang mungkin.

Pada dasarnya, apakah ini bijaksana? atau apakah ada cara yang lebih baik, bahkan lebih rapi untuk melakukan ini?


32
2018-02-17 10:03


asal


Jawaban:


Pendekatan ini adalah cara yang disukai untuk menyalin objek polimorfik karena ia melepaskan tanggung jawab untuk menentukan cara menyalin objek dari jenis acak ke objek itu, daripada mencoba menentukannya pada waktu kompilasi. Secara umum, jika Anda tidak tahu apa yang ditunjukan oleh pointer kelas dasar pada waktu kompilasi, Anda tidak mungkin tahu bagian mana dari sekian banyak kode potensial yang perlu Anda eksekusi untuk mendapatkan salinan yang benar. Karena itu, solusi apa pun yang bekerja akan membutuhkan pilihan kode yang dinamis, dan fungsi virtual adalah cara yang baik untuk melakukan ini.

Dua komentar pada kode aktual Anda. Pertama, C + + warisan memungkinkan kelas turunan yang mengesampingkan fungsi anggota kelas dasar untuk memiliki fungsi turunan mengembalikan pointer tipe yang lebih spesifik daripada versi kelas dasar. Ini disebut kovarians. Sebagai contoh, jika fungsi kelas dasar

virtual Base* clone() const;

Kemudian kelas turunan dapat mengesampingkannya sebagai

virtual Derived* clone() const;

Dan ini akan bekerja dengan baik. Ini memungkinkan Anda, misalnya, memiliki kode seperti ini:

Derived* d = // something...
Derived* copy = d->clone();

Yang, tanpa kelebihan kovarian, tidak akan legal.

Detail lainnya - dalam kode yang Anda miliki, Anda secara eksplisit static_cast pointer yang diturunkan ke pointer dasar dalam kode Anda. Ini sangat legal, tapi itu tidak perlu. C ++ secara implisit akan mengkonversi pointer kelas turunan ke pointer kelas dasar tanpa gips. Namun, jika Anda menggunakan ide tipe kovarian balik, ini tidak akan muncul karena jenis kembalinya akan cocok dengan jenis objek yang akan Anda buat.


33
2018-02-17 10:07



Perhatikan bahwa Anda tidak perlu static_cast di sana. Berasal * mengkonversi ke Basis * secara implisit. Anda benar-benar tidak perlu menggunakan dynamic_cast untuk itu, seperti yang disarankan Ken Wayne, karena tipe konkret diketahui pada waktu kompilasi, dan kompilator dapat memberi tahu Anda jika gips tidak diperbolehkan.

Adapun pendekatan, pola ini cukup standar untuk dibangun ke C # dan Java sebagai ICloneable dan Object.clone (), masing-masing.

Edit:

... atau apakah ada cara yang lebih baik dan lebih rapi untuk melakukan ini?

Anda bisa menggunakan "kelas dasar self-parameter", yang menghemat Anda mengimplementasikan fungsi clone () setiap kali. Anda hanya perlu mengimplementasikan konstruktor salin:

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}

2
2018-02-17 10:20



Ya, idemu adalah jalannya. Ini juga memungkinkan kelas turunan untuk memilih apakah mereka ingin melakukan salinan yang mendalam atau dangkal.

Saya memiliki satu titik (agak rewel) untuk referensi di masa mendatang: dalam hal keamanan, menggunakan dynamic_cast lebih disukai daripada static_cast untuk konversi polimorfik. Itu hanya salah satu hal yang menarik perhatian saya.


0
2018-02-17 10:12



template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

Ini hanya membutuhkan konstruktor salinan yang dapat diakses publik di setiap kelas turunan yang ingin Anda salin.


0
2018-05-05 17:56