Men-downcast shared_ptr <Base> ke shared_ptr <Derived>?

102

Pembaruan: shared_ptr dalam contoh ini seperti yang ada di Boost, tetapi tidak mendukung shared_polymorphic_downcast (atau dynamic_pointer_cast atau static_pointer_cast dalam hal ini)!

Saya mencoba untuk menginisialisasi penunjuk bersama ke kelas turunan tanpa kehilangan jumlah referensi:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Sejauh ini baik. Saya tidak berharap C ++ secara implisit mengubah Base * menjadi Derived *. Namun, saya menginginkan fungsionalitas yang diekspresikan oleh kode (yaitu, mempertahankan jumlah referensi sambil men-downcast penunjuk dasar). Pikiran pertama saya adalah menyediakan operator cor di Base sehingga konversi implisit ke Derived dapat terjadi (untuk pedants: Saya akan memeriksa apakah down cast valid, jangan khawatir):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Yah, itu tidak membantu. Sepertinya kompiler mengabaikan operator typecast saya. Ada ide bagaimana saya bisa membuat tugas shared_ptr bekerja? Untuk poin tambahan: tipe apa Base* constitu? const Base*Saya mengerti, tapi Base* const? Apa yang dimaksud constdalam kasus ini?

Lajos Nagy
sumber
Mengapa Anda membutuhkan shared_ptr <Derived>, bukan shared_ptr <Base>?
Tagihan
3
Karena saya ingin mengakses fungsionalitas di Berasal yang tidak ada di Basis, tanpa mengkloning objek (Saya ingin satu objek, direferensikan oleh dua petunjuk bersama). Ngomong-ngomong, mengapa operator pemeran tidak berfungsi?
Lajos Nagy

Jawaban:

109

Anda bisa menggunakan dynamic_pointer_cast. Ini didukung oleh std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Dokumentasi: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Juga, saya tidak merekomendasikan menggunakan operator cor di kelas dasar. Transmisi implisit seperti ini mungkin menjadi sumber bug dan kesalahan.

-Update: Jika tipenya tidak polimorfik, std::static_pointer_castboleh digunakan.

Massood Khaari
sumber
4
Saya tidak mengerti dari baris pertama yang tidak dia gunakan std::shared_ptr. Tapi dari komentar jawaban pertama saya menyimpulkan bahwa dia tidak menggunakan boost, jadi dia mungkin menggunakan std::shared_ptr.
Massood Khaari
BAIK. Maaf. Dia harus mengklarifikasi dengan lebih baik bahwa dia menggunakan penerapan kustom.
Massood Khaari
47

Saya berasumsi Anda menggunakan boost::shared_ptr... Saya pikir Anda ingin dynamic_pointer_castatau shared_polymorphic_downcast.

Namun, ini membutuhkan tipe polimorfik.

tipe apa Base* constitu? const Base*Saya mengerti, tapi Base* const? Apa yang dimaksud constdalam kasus ini?

  • const Base *adalah penunjuk yang bisa berubah ke sebuah konstanta Base.
  • Base const *adalah penunjuk yang bisa berubah ke sebuah konstanta Base.
  • Base * constadalah penunjuk konstan ke yang bisa berubah Base.
  • Base const * constadalah penunjuk konstan ke sebuah konstanta Base.

Berikut contoh minimalnya:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

Saya tidak yakin apakah itu disengaja bahwa contoh Anda membuat instance dari tipe dasar dan melemparkannya, tetapi berfungsi untuk menggambarkan perbedaan dengan baik.

The static_pointer_castkehendak "lakukan saja". Ini akan mengakibatkan perilaku tidak terdefinisi ( Derived*menunjuk ke memori yang dialokasikan untuk dan diinisialisasi oleh Base) dan kemungkinan besar akan menyebabkan crash, atau lebih buruk. Jumlah referensi baseakan bertambah.

Ini dynamic_pointer_castakan menghasilkan pointer nol. Jumlah referensi basetidak akan berubah.

The shared_polymorphic_downcastakan memiliki hasil yang sama sebagai pemain statis, tetapi akan memicu sebuah pernyataan, daripada tampak untuk berhasil dan yang mengarah ke perilaku undefined. Jumlah referensi baseakan bertambah.

Lihat (tautan mati) :

Terkadang agak sulit untuk memutuskan apakah akan menggunakan static_castatau dynamic_cast, dan Anda berharap dapat memiliki sedikit dari kedua dunia tersebut. Diketahui bahwa dynamic_cast memiliki overhead waktu proses, tetapi lebih aman, sedangkan static_cast tidak memiliki overhead sama sekali, tetapi mungkin gagal secara diam-diam. Betapa menyenangkannya jika Anda dapat menggunakan shared_dynamic_castdalam debug build, dan shared_static_castdalam build rilis. Nah, hal seperti itu sudah tersedia dan disebut shared_polymorphic_downcast.

Tim Sylvester
sumber
Sayangnya, solusi Anda bergantung pada fungsionalitas Boost yang sengaja dikecualikan dari implementasi shared_ptr tertentu yang kami gunakan (jangan tanya mengapa). Adapun penjelasan const, sekarang jauh lebih masuk akal.
Lajos Nagy
3
Singkatnya mengimplementasikan shared_ptrkonstruktor lain (mengambil static_cast_tagdan dynamic_cast_tag), tidak banyak yang dapat Anda lakukan. Apa pun yang Anda lakukan di luar shared_ptrtidak akan dapat mengelola refcount tersebut. - Dalam desain OO yang "sempurna", Anda selalu dapat menggunakan tipe dasar, dan tidak perlu mengetahui atau peduli apa tipe turunannya, karena semua fungsinya diekspos melalui antarmuka kelas dasar. Mungkin Anda hanya perlu memikirkan kembali mengapa Anda perlu down-cast di tempat pertama.
Tim Sylvester
1
@ Tim Sylvester: tapi, C ++ bukanlah bahasa OO yang "sempurna"! :-) para down-cast memiliki tempat mereka dalam bahasa OO yang tidak sempurna
Steve Folly
4

Jika seseorang sampai di sini dengan boost :: shared_ptr ...

Ini adalah bagaimana Anda bisa downcast ke Boost shared_ptr diturunkan. Asumsi Derived mewarisi dari Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Pastikan class / struct 'Base' memiliki setidaknya satu fungsi virtual. Penghancur virtual juga berfungsi.

Mitendra
sumber