Meneruskan shared_ptr <Derived> sebagai shared_ptr <Base>

93

Apa metode terbaik untuk meneruskan sebuah shared_ptrdari tipe turunan ke fungsi yang mengambil shared_ptrtipe dasar?

Saya biasanya melewatkan shared_ptrreferensi untuk menghindari salinan yang tidak perlu:

int foo(const shared_ptr<bar>& ptr);

tetapi ini tidak berhasil jika saya mencoba melakukan sesuatu seperti

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

Saya bisa menggunakan

foo(dynamic_pointer_cast<Base, Derived>(bar));

tetapi ini tampaknya kurang optimal karena dua alasan:

  • A dynamic_casttampaknya agak berlebihan untuk pemeran turunan-ke-dasar sederhana.
  • Seperti yang saya pahami, dynamic_pointer_castmembuat salinan (meskipun sementara) dari pointer untuk diteruskan ke fungsi.

Apakah ada solusi yang lebih baik?

Pembaruan untuk anak cucu:

Ternyata itu adalah masalah file header yang hilang. Juga, apa yang saya coba lakukan di sini dianggap sebagai antipattern. Umumnya,

  • Fungsi yang tidak mempengaruhi masa pakai objek (yaitu, objek tetap valid selama durasi fungsi) harus menggunakan referensi atau penunjuk biasa, misalnya int foo(bar& b).

  • Fungsi yang mengkonsumsi suatu objek (yaitu pengguna akhir dari objek tertentu) harus mengambil unique_ptrnilai, misalnya int foo(unique_ptr<bar> b). Penelepon harus std::movememasukkan nilai ke dalam fungsi.

  • Fungsi yang memperpanjang umur suatu objek harus shared_ptrberdasarkan nilai, misalnya int foo(shared_ptr<bar> b). Nasihat biasa untuk menghindari referensi melingkar berlaku.

Lihat pembicaraan Kembali ke Dasar Herb Sutter untuk detailnya.

Matt Kline
sumber
8
Mengapa Anda ingin lulus shared_ptr? Mengapa tidak ada referensi konstanta bar?
ipc
2
Setiap dynamicpemain hanya diperlukan untuk downcasting. Juga, meneruskan pointer yang diturunkan seharusnya bekerja dengan baik. Ini akan membuat baru shared_ptrdengan refcount yang sama (dan meningkatkannya) dan penunjuk ke basis, yang kemudian mengikat ke referensi const. Karena Anda sudah mengambil referensi, saya tidak mengerti mengapa Anda ingin mengambilnya shared_ptrsama sekali. Terima Base const&dan telepon foo(*bar).
Xeo
@ Xeo: Meneruskan penunjuk turunan (yaitu foo(bar)) tidak berfungsi, setidaknya di MSVC 2010.
Matt Kline
1
Apa yang Anda maksud dengan "jelas tidak berhasil"? Kode mengkompilasi dan berperilaku dengan benar; apakah Anda bertanya bagaimana cara menghindari pembuatan sementara shared_ptruntuk diteruskan ke fungsi? Saya cukup yakin tidak ada cara untuk menghindarinya.
Mike Seymour
1
@ Set: Saya tidak setuju. Saya pikir ada alasan untuk meneruskan penunjuk bersama berdasarkan nilai, dan hanya ada sedikit alasan untuk meneruskan penunjuk bersama dengan referensi (dan semua ini tanpa menganjurkan salinan yang tidak diperlukan). Alasannya di sini stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Jawaban:

47

Meskipun Basedan Derivedyang kovarian dan pointer baku untuk mereka akan bertindak sesuai, shared_ptr<Base>dan shared_ptr<Derived>yang tidak kovarian. Ini dynamic_pointer_castadalah cara yang benar dan paling sederhana untuk menangani masalah ini.

( Edit: static_pointer_cast akan lebih sesuai karena Anda melakukan transmisi dari turunan ke basis, yang aman dan tidak memerlukan pemeriksaan waktu proses. Lihat komentar di bawah.)

Namun, jika foo()fungsi Anda tidak ingin mengambil bagian dalam memperpanjang masa pakai (atau, lebih tepatnya, mengambil bagian dalam kepemilikan bersama objek), sebaiknya terima const Base&dan dereferensi shared_ptrsaat meneruskannya foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Selain itu, karena shared_ptrtipe tidak bisa menjadi kovarian, aturan konversi implisit di seluruh tipe kembalian kovarian tidak berlaku saat mengembalikan tipe shared_ptr<T>.

Bret Kuhns
sumber
39
Mereka bukan kovarian, tetapi shared_ptr<Derived>secara implisit dapat dikonversi shared_ptr<Base>, jadi kode harus bekerja tanpa kesalahan casting.
Mike Seymour
9
Um, shared_ptr<Ty>memiliki konstruktor yang mengambil a shared_ptr<Other>dan melakukan konversi yang sesuai jika Ty*secara implisit dapat dikonversi Other*. Dan jika pemeran diperlukan, static_pointer_castapakah yang sesuai di sini, bukan dynamic_pointer_cast.
Pete Becker
Benar, tetapi tidak dengan parameter referensinya, seperti dalam pertanyaan. Bagaimanapun, dia perlu menyalinnya. Tapi, jika dia menggunakan referensi to shared_ptruntuk menghindari jumlah referensi, maka sebenarnya tidak ada alasan bagus untuk menggunakan a shared_ptrdi tempat pertama. Sebaiknya gunakan const Base&sebagai gantinya.
Bret Kuhns
@PeteBecker Lihat komentar saya kepada Mike tentang konstruktor konversi. Sejujurnya saya tidak tahu static_pointer_cast, terima kasih.
Bret Kuhns
1
@TanveerBadar Tidak yakin. Mungkin ini gagal untuk dikompilasi pada tahun 2012? (secara khusus menggunakan Visual Studio 2010 atau 2012). Tapi Anda benar, kode OP harus benar-benar dikompilasi jika definisi lengkap dari / diturunkan secara publik / kelas terlihat oleh kompiler.
Bret Kuhns
32

Ini juga akan terjadi jika Anda lupa menentukan warisan publik pada kelas turunan, yaitu jika seperti saya Anda menulis ini:

class Derived : Base
{
};
dshepherd
sumber
classadalah untuk parameter template; structadalah untuk menentukan kelas. (Ini paling banyak 45% lelucon.)
Davis Herring
Ini pasti harus dipertimbangkan solusinya, tidak perlu ada pemeran karena hanya merindukan publik.
Alexis Paques
12

Sepertinya Anda berusaha terlalu keras. shared_ptrmurah untuk disalin; itulah salah satu tujuannya. Mengedarkannya dengan referensi tidak akan menghasilkan banyak hal. Jika Anda tidak ingin berbagi, teruskan penunjuk mentah.

Meskipun demikian, ada dua cara untuk melakukan ini yang dapat saya pikirkan:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));
Pete Becker
sumber
9
Tidak, mereka tidak murah untuk ditiru, mereka harus diberikan referensi bila memungkinkan.
Seth Carnegie
6
@SethCarnegie - apakah Herb memprofilkan kode Anda untuk melihat apakah melewatkan nilai merupakan hambatan?
Pete Becker
25
@SethCarnegie - itu tidak menjawab pertanyaan yang saya ajukan. Dan, untuk apa nilainya, saya menulis shared_ptrimplementasi yang dikirimkan Microsoft.
Pete Becker
6
@ SethCarnegie - Anda memiliki heuristik mundur. Pengoptimalan tangan umumnya tidak boleh dilakukan kecuali Anda dapat menunjukkan bahwa itu diperlukan.
Pete Becker
21
Ini hanya pengoptimalan "prematur" jika Anda perlu mengusahakannya. Saya melihat tidak ada masalah dalam mengadopsi idiom yang efisien daripada yang tidak efisien, apakah itu membuat perbedaan dalam konteks tertentu atau tidak.
Markus Tebusan
11

Periksa juga apakah #includefile header yang berisi deklarasi lengkap kelas turunan ada di file sumber Anda.

Saya punya masalah ini. Itu std::shared<derived>tidak akan dilemparkan ke std::shared<base>. Saya telah mendeklarasikan kedua kelas ke depan sehingga saya dapat menyimpan petunjuk kepada mereka, tetapi karena saya tidak memiliki #includekompilator tidak dapat melihat bahwa satu kelas diturunkan dari yang lain.

Phil Rosenberg
sumber
1
Wow, saya tidak mengharapkannya tetapi ini memperbaikinya untuk saya. Saya sangat berhati-hati dan hanya menyertakan file header jika saya membutuhkannya sehingga beberapa di antaranya hanya ada di file sumber dan teruskan mendeklarasikannya di header seperti yang Anda katakan.
jigglypuff
Kompiler bodoh itu bodoh. Ini adalah masalah saya. Terima kasih!
Tanveer Badar