Shared_ptr <Base> auf shared_ptr <Derived> übertragen?

102

Update: Das shared_ptr in diesem Beispiel ähnelt dem in Boost, unterstützt jedoch nicht shared_polymorphic_downcast (oder dynamic_pointer_cast oder static_pointer_cast)!

Ich versuche, einen gemeinsam genutzten Zeiger auf eine abgeleitete Klasse zu initialisieren, ohne den Referenzzähler zu verlieren:

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;  

So weit, ist es gut. Ich hatte nicht erwartet, dass C ++ Base * implizit in Derived * konvertiert. Ich möchte jedoch, dass die Funktionalität durch den Code ausgedrückt wird (dh die Referenzanzahl wird beibehalten, während der Basiszeiger heruntergespielt wird). Mein erster Gedanke war, einen Cast-Operator in Base bereitzustellen, damit eine implizite Konvertierung in Derived stattfinden kann (für Pedanten: Ich würde überprüfen, ob der Down-Cast gültig ist, keine Sorge):

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

Nun, es hat nicht geholfen. Es scheint, dass der Compiler meinen Typecast-Operator völlig ignoriert hat. Irgendwelche Ideen, wie ich die shared_ptr-Zuweisung zum Laufen bringen könnte? Für zusätzliche Punkte: Was für ein Typ Base* constist das? const Base*Ich verstehe, aber Base* const? Worauf bezieht constsich in diesem Fall?

Lajos Nagy
quelle
Warum benötigen Sie einen shared_ptr <Derived> anstelle von shared_ptr <Base>?
Bill
3
Weil ich auf Funktionen in Derived zugreifen möchte, die sich nicht in Base befinden, ohne das Objekt zu klonen (ich möchte ein einzelnes Objekt, auf das von zwei gemeinsam genutzten Zeigern verwiesen wird). Warum arbeiten die Cast-Operatoren übrigens nicht?
Lajos Nagy

Antworten:

108

Sie können verwenden dynamic_pointer_cast. Es wird unterstützt von std::shared_ptr.

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

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

Außerdem empfehle ich nicht, den Cast-Operator in der Basisklasse zu verwenden. Implizites Casting wie dieses kann zur Quelle von Fehlern und Fehlern werden.

-Update: Wenn der Typ nicht polymorph ist, std::static_pointer_castkann verwendet werden.

Massood Khaari
quelle
4
Ich habe von der ersten Zeile an nicht verstanden, dass er nicht benutzt std::shared_ptr. Aber aus den Kommentaren der ersten Antwort schloss ich, dass er keinen Boost verwendet, also verwendet er möglicherweise std::shared_ptr.
Massood Khaari
OK. Es tut uns leid. Er sollte besser klarstellen, dass er eine benutzerdefinierte Implementierung verwendet.
Massood Khaari
47

Ich nehme an, Sie verwenden boost::shared_ptr... Ich denke, Sie wollen dynamic_pointer_castoder shared_polymorphic_downcast.

Diese erfordern jedoch polymorphe Typen.

Was für ein Typ Base* constist das? const Base*Ich verstehe, aber Base* const? Worauf bezieht constsich in diesem Fall?

  • const Base *ist ein veränderlicher Zeiger auf eine Konstante Base.
  • Base const *ist ein veränderlicher Zeiger auf eine Konstante Base.
  • Base * constist ein konstanter Zeiger auf eine veränderbare Base.
  • Base const * constist ein konstanter Zeiger auf eine Konstante Base.

Hier ist ein minimales Beispiel:

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);

Ich bin nicht sicher, ob es beabsichtigt war, dass Ihr Beispiel eine Instanz des Basistyps erstellt und umwandelt, aber es dient dazu, den Unterschied gut zu veranschaulichen.

Der static_pointer_castWille "mach es einfach". Dies führt zu undefiniertem Verhalten (ein Derived*Hinweis auf den Speicher, der zugewiesen und von initialisiert wurde Base) und führt wahrscheinlich zu einem Absturz oder schlimmerem. Der Referenzzähler basewird erhöht.

Das dynamic_pointer_castführt zu einem Nullzeiger. Die Referenzanzahl basebleibt unverändert.

Das shared_polymorphic_downcastErgebnis hat das gleiche Ergebnis wie eine statische Besetzung, löst jedoch eine Behauptung aus, anstatt erfolgreich zu sein und zu undefiniertem Verhalten zu führen. Der Referenzzähler basewird erhöht.

Siehe (toter Link) :

Manchmal ist es etwas schwierig zu entscheiden, ob static_castoder verwendet werden soll dynamic_cast, und Sie wünschen sich, Sie könnten ein bisschen von beiden Welten haben. Es ist bekannt, dass dynamic_cast einen Laufzeit-Overhead hat, aber sicherer ist, während static_cast überhaupt keinen Overhead hat, aber möglicherweise unbemerkt fehlschlägt. Wie schön wäre es, wenn Sie shared_dynamic_castin Debug-Builds und shared_static_castin Release-Builds verwenden könnten . Nun, so etwas ist schon verfügbar und heißt shared_polymorphic_downcast.

Tim Sylvester
quelle
Leider hängt Ihre Lösung von der Boost-Funktionalität ab, die absichtlich von der von uns verwendeten Implementierung von shared_ptr ausgeschlossen wurde (fragen Sie nicht warum). Die konstante Erklärung ist jetzt viel sinnvoller.
Lajos Nagy
3
Ohne die anderen shared_ptrKonstruktoren (take static_cast_tagund dynamic_cast_tag) zu implementieren , können Sie nicht viel tun. Alles, was Sie draußen tun shared_ptr, kann die Nachzählung nicht verwalten. - In einem "perfekten" OO-Design können Sie immer den Basistyp verwenden und müssen nie wissen oder sich darum kümmern, was der abgeleitete Typ ist, da seine gesamte Funktionalität über Schnittstellen der Basisklasse verfügbar gemacht wird. Vielleicht müssen Sie nur überlegen, warum Sie überhaupt runterwerfen müssen.
Tim Sylvester
1
@ Tim Sylvester: Aber C ++ ist keine "perfekte" OO-Sprache! :-) Downcasts haben ihren Platz in einer nicht perfekten OO-Sprache
Steve Folly
4

Wenn jemand mit boost :: shared_ptr hierher kommt ...

Auf diese Weise können Sie auf den abgeleiteten Boost shared_ptr herunterstufen. Angenommen, Abgeleitet erbt von 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;

Stellen Sie sicher, dass die Basisklasse / Struktur mindestens eine virtuelle Funktion hat. Ein virtueller Destruktor funktioniert ebenfalls.

Mitendra
quelle