Shared_ptr <Derived> shared_ptr <Base> olarak iletiliyor


93

shared_ptrTüretilmiş türden a'yı shared_ptrtemel türden alan bir işleve geçirmenin en iyi yöntemi nedir ?

shared_ptrGereksiz bir kopyadan kaçınmak için genellikle s'yi referans olarak geçiririm:

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

ama böyle bir şey yapmaya çalışırsam bu işe yaramaz

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

...

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

kullanabilirim

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

ancak bu, iki nedenden dolayı optimalin altında görünüyor:

  • A dynamic_cast, basit bir türetilmiş tabana döküm için biraz aşırı görünüyor.
  • Anladığım kadarıyla dynamic_pointer_cast, işleve geçmek için işaretçinin (geçici de olsa) bir kopyasını oluşturuyor.

Daha iyi bir çözüm var mı?

Gelecek nesil için güncelleme:

Eksik bir başlık dosyası sorunu olduğu ortaya çıktı. Ayrıca, burada yapmaya çalıştığım şey bir anti-model olarak kabul ediliyor. Genel olarak,

  • Bir nesnenin yaşam süresini etkilemeyen işlevler (yani nesne, işlevin süresi boyunca geçerli kalır) düz bir referans veya işaretçi almalıdır, ör int foo(bar& b).

  • Bir nesneyi tüketen işlevler (yani belirli bir nesnenin son kullanıcılarıdır) bir unique_ptrdeğer almalıdır , ör int foo(unique_ptr<bar> b). Arayanlar std::move, işleve değer girmelidir .

  • Bir nesnenin ömrünü uzatan işlevler bir shared_ptrdeğer almalıdır , örn int foo(shared_ptr<bar> b). Döngüsel referanslardan kaçınmak için olağan tavsiyeler geçerlidir.

Ayrıntılar için Herb Sutter's Back to Basics konuşmasına bakın.


8
Neden a geçmek istiyorsun shared_ptr? Neden sabit referansı yok?
ipc

2
Herhangi bir dynamicoyuncu kadrosu yalnızca aşağı izleme için gereklidir. Ayrıca, türetilmiş göstericiyi geçirmek de gayet iyi çalışmalıdır. shared_ptrAynı refcount ile yeni bir tane oluşturacak (ve artıracaktır) ve tabana bir işaretçi oluşturacak ve ardından const referansına bağlanacaktır. Zaten bir referans aldığınız için, ancak neden bir referans almak istediğinizi anlamıyorum shared_ptr. Bir al Base const&ve ara foo(*bar).
Xeo

@Xeo: Türetilmiş göstericiyi (yani foo(bar)) geçmek , en azından MSVC 2010'da çalışmıyor.
Matt Kline

1
"Belli ki çalışmıyor" derken neyi kastediyorsunuz? Kod doğru şekilde derlenir ve davranır; İşleve shared_ptrgeçmek için bir geçici oluşturmaktan nasıl kaçınılacağını mı soruyorsunuz ? Bundan kaçınmanın bir yolu olmadığına oldukça eminim.
Mike Seymour

1
@Seth: Katılmıyorum. Bence paylaşılan bir göstericiyi değere göre geçirmek için bir neden var ve paylaşılan bir işaretçiyi referansla geçirmek için çok az neden var (ve tüm bunlar gereksiz kopyaları savunmadan). Sebep burada stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Yanıtlar:


47

Her ne kadar Baseve Derivedvardır onlara kovaryant ve çiğ işaretçileri göre hareket, olacak shared_ptr<Base>ve shared_ptr<Derived>olan değil eşdeğişkin. dynamic_pointer_castBu problemi çözmek için doğru ve kolay yoludur.

( Düzenleme: static_pointer_cast daha uygun olur çünkü türetilmişten tabana çeviriyorsunuz, bu güvenli ve çalışma zamanı kontrolleri gerektirmez. Aşağıdaki yorumlara bakın.)

Bununla birlikte, foo()işleviniz ömrünü uzatmada yer almak istemiyorsa (veya daha ziyade nesnenin ortak mülkiyetinde yer almak), o zaman en iyisi bir kabul etmek const Base&ve shared_ptronu iletirken ondan vazgeçmektir foo().

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

Bir kenara, shared_ptrtürler ortak değişken olamayacağından, kovaryant dönüş türlerindeki örtük dönüşüm kuralları, türlerini döndürürken uygulanmaz shared_ptr<T>.


39
Kovaryant değiller, ancak shared_ptr<Derived>dolaylı olarak dönüştürülebilirler shared_ptr<Base>, bu nedenle kod, döküm saçmalıkları olmadan çalışmalıdır.
Mike Seymour

9
Um, shared_ptr<Ty>a alan shared_ptr<Other>ve Ty*örtük olarak dönüştürülebilirse uygun dönüşümü yapan bir kurucuya sahiptir Other*. Ve bir alçıya ihtiyaç varsa, static_pointer_castburada uygun olanı değil dynamic_pointer_cast.
Pete Becker

Doğru, ancak soruda olduğu gibi referans parametresiyle değil. Ne olursa olsun bir kopya yapması gerekecekti. Ancak, shared_ptrreferans sayısından kaçınmak için refs to 's kullanıyorsa , o zaman shared_ptrilk etapta a kullanmak için gerçekten iyi bir neden yoktur . Bunun const Base&yerine kullanmak en iyisidir .
Bret Kuhns

@PeteBecker Dönüşüm oluşturucu hakkında Mike'a yaptığım yorumu görün. Gerçekten bilmiyordum static_pointer_cast, teşekkürler.
Bret Kuhns

1
@TanveerBadar Emin değilim. Belki de bu 2012'de derlenemedi? (özellikle Visual Studio 2010 veya 2012 kullanarak). Ama kesinlikle haklısınız, eğer / publicly türetilmiş / sınıfın tam tanımı derleyici tarafından görülebiliyorsa, OP'nin kodu kesinlikle derlenmelidir.
Bret Kuhns

32

Bu , türetilmiş sınıfta genel kalıtımı belirtmeyi unuttuysanız da olur , yani benim gibi bunu yazarsanız:

class Derived : Base
{
};

classşablon parametreleri içindir; structsınıfları tanımlamak içindir. (Bu en fazla% 45 şaka.)
Davis Herring

Bu kesinlikle çözüm olarak düşünülmelidir, sadece halkın eksik olması nedeniyle bir oyuncu kadrosuna gerek yoktur.
Alexis Paques

12

Çok çabalıyormuşsun gibi geliyor. shared_ptrkopyalamak ucuzdur; bu onun hedeflerinden biridir. Bunları referans olarak iletmek pek bir şey başaramıyor. Paylaşmak istemiyorsanız, ham işaretçiyi iletin.

Bununla birlikte, bunu yapmanın kafamın dışında düşünebileceğim iki yolu var:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
Hayır, kopyalanması ucuz değil, mümkün olduğunda referans olarak aktarılmalıdır.
Seth Carnegie

6
@SethCarnegie - Herb, değere göre geçişin bir darboğaz olup olmadığını görmek için kodunuzun profilini çıkardı mı?
Pete Becker

25
@SethCarnegie - bu sorduğum soruya cevap vermiyor. Ve değeri ne olursa olsun shared_ptr, Microsoft'un gönderdiği uygulamayı yazdım .
Pete Becker

6
@SethCarnegie - Sezgisel geriye doğru sahipsiniz. El optimizasyonlar genellikle gerektiği değil yapılabilir sürece onlar ihtiyaç olduğunu gösterebilir.
Pete Becker

21
Sadece üzerinde çalışmanız gerekiyorsa "erken" optimizasyondur. Belirli bir bağlamda bir fark yaratıp yaratmadığına bakılmaksızın, verimli deyimleri verimsiz olanlara göre benimsemekte hiçbir sorun görmüyorum.
Mark Ransom

11

Ayrıca #includetüretilmiş sınıfın tam bildirimini içeren başlık dosyasının kaynak dosyanızda olup olmadığını kontrol edin .

Bu sorunu yaşadım. std::shared<derived>Artığını olmaz std::shared<base>. Her iki sınıfı da onlara işaret edebilmek için ileri bildirmiştim, ancak #includederleyiciye sahip olmadığım için bir sınıfın diğerinden türetildiğini göremedim.


1
Vay canına, beklemiyordum ama bu benim için sorunu çözdü. Fazladan dikkatli davranıyordum ve sadece onlara ihtiyacım olan başlık dosyalarını ekliyordum, bu yüzden bazıları sadece kaynak dosyalaydı ve söylediğiniz gibi bunları başlıklarda bildiriyorlardı.
jigglypuff

Aptal derleyici aptal. Bu benim sorunumdu. Teşekkür ederim!
Tanveer Badar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.