boost::dynamic_pointer_cast auf Referenzen
-
Folgendes:
#include <boost/shared_ptr.hpp> using boost::shared_ptr; struct base { virtual ~base(){} }; struct derived : public base {}; shared_ptr<derived>& ugly_downcast(shared_ptr<base>& p) { return reinterpret_cast<shared_ptr<derived>&>(p); } int main() { boost::shared_ptr<base> p(new derived()); boost::shared_ptr<derived> d = boost::dynamic_pointer_cast<derived>(p); boost::shared_ptr<derived>& dref = ugly_downcast(p); }Kann mir jemand sagen, wie ich in Zeile 10 ohne diesen reinterpret_cast auskomme oder weiss jemand sogar eine Lösung, mit der man auf hässliche Hilfsfunktion verzichten kann?
Vielen Dank!
-
dein ugly_downcast ist nicht nur hässlich, sondern auch extrem fehleranfällig, wenn er denn überhaupt definiertes Verhalten erzeugt. Was wenn da garkein derived in dem shared_ptr<base> drin steckt?
schmeiß den downcast weg und benutze nur den boost::dynamic_pointer_cast. Und Zeile 17 dann
boost::shared_ptr<derived>& dref = d;fertig.
-
pumuckl schrieb:
schmeiß den downcast weg und benutze nur den boost::dynamic_pointer_cast.
Ich brauch aber eine Referenz auf das originale p, nicht auf eine lokale Kopie

-
castren schrieb:
Ich brauch aber eine Referenz auf das originale p, nicht auf eine lokale Kopie

Kannst du mal den konkreten Fall zeigen, in dem du das brauchst? Lässt sich ziemlich sicher auch anders lösen...
-
Mit dem Design bin ich wirklich noch nicht vollständig zufrieden, aber wenigstens läuft es. Ich habe einen Syntaxbaum, genauer eine Klasse Ausdruck mit Typ-ID und einige abgeleitete Klassen (deren Typ sich durch die ID genau bestimmen lässt), die wiederum einige Ausdrücke enthalten. Um Object Slicing zu vermeiden halte ich diese Unterausdrücke als Liste von shared_ptr<Ausdruck>.
Funktionen, die diesen Baum umbauen, übergebe ich eine Referenz auf den shared_ptr. Jetzt gibt es einige Funktionen, die nur die abgeleitete Klasse Element bearbeiten und es teilweise auch durch ein anderes Element ersetzen. Diese brauchen eine Referenz weil sie shared_ptr::reset aufrufen. Wenn ich diesen Funktionen (ich rufe immer gleich mehrere auf) einen shared_ptr<Element> übergeben kann, können diese auf ein schon geschehenes und daher eigentlich überflüssiges Typ-Checking und Casten verzichten, die sie bei einem shared_ptr<Ausdruck> machen müssten.
Dein Einwand mit der Fehleranfälligkeit trifft hier also nur eingeschränkt zu, da ich mir ja sicher bin, dass ich einen shared_ptr mit einem Zeiger auf Ausdruck vor mir habe.
-
Wenn ich das richtig verstehe, passiert bei dir folgendes (pseudocode)
shared_ptr<base> base_ptr; if (base_ptr points to derived) function_for_derived( derived_referenz_auf_base_ptr )function_for_derived( derived_referenz_auf_base_ptr ) { /* ... */ derived_referenz_auf_base_ptr.reset(new derived); //(1) //oder derived_referenz_auf_base_ptr.reset(); //(2) }Ich würd das vermutlich so machen:
shared_ptr<base> base_ptr; function_conditional_for_derived( base_ptr );function_conditional_for_derived( base_ptr ) { shared_ptr<derived> derived_ptr = boost::dynamic_pointer_cast<derived>(base_ptr); if (!derived_ptr) return; /* arbeite mit derived_ptr */ base_ptr.reset(new derived); //(1) //oder base_ptr.reset(); //(2) }Ohne mehrfaches typechecking und ohne hässliche und trotz allem unsichere reinterpret_casts
-
pumuckl schrieb:
Wenn ich das richtig verstehe, passiert bei dir folgendes
Ja, das hast du richtig verstanden.
pumuckl schrieb:
shared_ptr<derived> derived_ptr = boost::dynamic_pointer_cast<derived>(base_ptr); if (!derived_ptr) return; /* arbeite mit derived_ptr */Um diese drei Zeilen geht es, die wollte ich vermeiden. Denn erstens sieht man der Funktionssignatur nicht an, dass ein derived_ptr gewünscht ist und zweitens würden sich diese zusätzlichen Zeilen relativ oft wiederholen, also trotzdem mehrfaches Typechecking erfordern. Es ist nicht nur eine Funktion function_conditional_for_derived, sondern viele und die werden alle nacheinander aufgerufen.
Ich habe mir auch schon überlegt, das reset (mit Parameter) der aufrufenden Funktion zu überlassen. Das ginge dann zwar mit dem boost::dynamic_pointer_cast wieder, wäre aber wieder ein unschöner Hack.
Irgendwie scheint es auf einen shared_ptr<shared_ptr<base>> herauszulaufen :-|
Trotz allem danke für deine Antwort!
-
castren schrieb:
pumuckl schrieb:
schmeiß den downcast weg und benutze nur den boost::dynamic_pointer_cast.
Ich brauch aber eine Referenz auf das originale p, nicht auf eine lokale Kopie

Das geht aber nicht.
Weder mit shared_ptr, noch mit rohen Zeigern.Wenn wir klassen Base und Derived haben, und Derived ist von Base abgeleitet...
Dann sind zwar Base und Derived "verwandte" Typen, so dass man Base* <-> Derived* casten kann, oder auch Base& <-> Derived&.
Allerdings sind Base* und Derived* nicht verwandt, das sind 100% unterschiedliche Typen.
Daher kann man auch nicht Base** <-> Derived** casten, und genau so nicht Base*& <-> Derived*&.Dass es manchmal trotzdem funktioniert, ist wieder eine andere Sache, aber laut Standard ist es UB.
Und es gibt genügend Fälle, wo es auch in der Praxis nimmer geht.
z.B. sobald Base nicht die 1. sondern die 2., 3. ... Basisklasse von Derived ist.
-
Danke, hustbaer. Jetzt weiss ich zumindest, weshalb meine gesitteten Versuche fehlgeschlagen sind.
-
castren schrieb:
erstens sieht man der Funktionssignatur nicht an, dass ein derived_ptr gewünscht ist
Ist auch nicht gewünscht. Die Funktion akzeptiert alle base_ptr und verarbeitet davon die, die ihr passen. Der typecheck vor der Funktion wird in die Funktion reingeschoben.
und zweitens würden sich diese zusätzlichen Zeilen relativ oft wiederholen, also trotzdem mehrfaches Typechecking erfordern. Es ist nicht nur eine Funktion function_conditional_for_derived, sondern viele und die werden alle nacheinander aufgerufen.
Und alle resetten den Pointer? Dann gib mal ein etwas ausführlicheres Beispiel (keine 500 Zeilen, aber so 30-50).
Am Ende wirds vermutlich heißen "einen Tod musst du sterben" - da du nur auf dem base_ptr resetten kannst, kann das resetten nicht in einer "tieferen" Funktion als der cast geschehen.
-
Doch, man könnte "unten" die Kopie resetten, und dann beim "wieder hinaufsteigen" gucken ob die Kopie resettet wurde, und ggf. das Original resetten.
-
pumuckl schrieb:
castren schrieb:
erstens sieht man der Funktionssignatur nicht an, dass ein derived_ptr gewünscht ist
Ist auch nicht gewünscht. Die Funktion akzeptiert alle base_ptr und verarbeitet davon die, die ihr passen. Der typecheck vor der Funktion wird in die Funktion reingeschoben.
Jetzt ist gerade ein Fall aufgetreten in dem das zutrifft. Also in etwa:
function_for_derived( derived_referenz_auf_base_ptr ) { derived_referenz_auf_base_ptr.reset( new base ) // ups }Damit hast du Recht, dass das Argument eine base_ptr_referenz sein sollte. Dadurch bin ich quasi gezwungen in jeder function_for_derived einen boost::dynamic_pointer_cast durchzuführen und diesen auf 0 zu testen. Also genau so, wie du vorgeschlagen hast.
Völlig zufriedengestellt bin ich damit jedoch nicht, da das unnötig Laufzeit kostet, mit den beiden fast gleichen shared_ptrs etwas fehleranfälliger wird und mehrfache Codeverdoppelung bewirkt. Störend ist davon aber nur letzteres.
Dann gib mal ein etwas ausführlicheres Beispiel
function_conditional_for_derived_1( base_ptr_referenz ) { shared_ptr<derived> derived_ptr = boost::dynamic_pointer_cast<derived>( base_ptr ); if (!derived_ptr) return; /* arbeite mit derived_ptr aber resette mit base_ptr */ if ( condition using derived_ptr ) base_ptr = new derived_ptr; } function_conditional_for_derived_2( base_ptr_referenz ) { shared_ptr<derived> derived_ptr = boost::dynamic_pointer_cast<derived>(base_ptr); if (!derived_ptr) return; /* arbeite mit derived_ptr aber resette mit base_ptr */ ... } function_conditional_for_derived_3( base_ptr_referenz ) { /* Cast */ ... } function_conditional_for_derived_4( base_ptr_referenz ) { /* Cast */ ... } function_conditional_main { if (base_ptr.typeid == type_derived) foreach function in function_conditional_for_derived_array function( base_ptr ) }Schön ist das nicht, aber wenn es wirklich nicht besser geht, dann soll mich das nicht weiter stören. Immerhin bin ich jetzt den reinterpret_cast los.
-
Wenn du Code-Duplicatoin vermeiden willst, mach das doch. Mal ein Ansatz:
template <class Derived, class Base> void conditionalReplaceSharedPtr(shared_ptr<Base>& base_ptr, function<shared_ptr<Base>(shared_ptr<Derived>)> const& f) //boost::function oder std::function { shared_ptr<Derived> derived_ptr = dynamic_pointer_cast<Derived>(base_ptr); if (!derived_ptr) return; shared_ptr<Base> newbase_ptr = f(derived_ptr); if (newbase_ptr) { base_ptr = newbase_ptr; } }Damit hast du den Pointer_cast genau einmal im Code.
Funktionierendes Beispiel zur Anwendung: