In C ++ führt die T q = dynamic_cast<T>(p);
Konstruktion eine Laufzeitumwandlung eines Zeigers p
auf einen anderen Zeigertyp durch T
, der in der Vererbungshierarchie des dynamischen Typs von *p
angezeigt werden muss, um erfolgreich zu sein. Das ist alles in Ordnung und gut.
Es ist jedoch auch eine Ausführung möglich dynamic_cast<void*>(p)
, bei der einfach ein Zeiger auf das "am meisten abgeleitete Objekt" zurückgegeben wird (siehe 5.2.7 :: 7 in C ++ 11). Ich verstehe, dass diese Funktion bei der Implementierung der dynamischen Besetzung wahrscheinlich kostenlos zur Verfügung steht, aber ist sie in der Praxis nützlich? Immerhin ist der Rückgabetyp bestenfalls void*
, also was nützt das?
c++
dynamic-cast
Kerrek SB
quelle
quelle
p
... gibt es eine Situation wop1 == p2
, aberdynamic_cast<void*>(p1) != dynamic_cast<void*>(p2)
?p1 != p2
, aber tatsächlich zeigen sie auf dasselbe Objekt. Ich denke, wenn wir einen Index hättenvoid *
, wäre das sinnvoll. (Obwohl der leere Zeiger selbst nicht mehr verwendbar wäre.)Antworten:
Das
dynamic_cast<void*>()
kann in der Tat verwendet werden, um die Identität zu überprüfen, selbst wenn es sich um Mehrfachvererbung handelt.Versuchen Sie diesen Code:
#include <iostream> class B { public: virtual ~B() {} }; class D1 : public B { }; class D2 : public B { }; class DD : public D1, public D2 { }; namespace { bool eq(B* b1, B* b2) { return b1 == b2; } bool eqdc(B* b1, B *b2) { return dynamic_cast<void*>(b1) == dynamic_cast<void*>(b2); } }; int main() { DD *dd = new DD(); D1 *d1 = dynamic_cast<D1*>(dd); D2 *d2 = dynamic_cast<D2*>(dd); std::cout << "eq: " << eq(d1, d2) << ", eqdc: " << eqdc(d1, d2) << "\n"; return 0; }
Ausgabe:
eq: 0, eqdc: 1
quelle
Denken Sie daran, dass Sie mit C ++ die Dinge auf die alte C-Art erledigen können.
Angenommen, ich habe eine API, in der ich gezwungen bin, einen Objektzeiger durch den Typ zu schmuggeln
void*
, aber wo der Rückruf, an den er schließlich übergeben wird, seinen dynamischen Typ kennt:struct BaseClass { typedef void(*callback_type)(void*); virtual callback_type get_callback(void) = 0; virtual ~BaseClass() {} }; struct ActualType: BaseClass { callback_type get_callback(void) { return my_callback; } static void my_callback(void *p) { ActualType *self = static_cast<ActualType*>(p); ... } }; void register_callback(BaseClass *p) { // service.register_listener(p->get_callback(), p); // WRONG! service.register_listener(p->get_callback(), dynamic_cast<void*>(p)); }
Die falsche! Code ist falsch, weil er bei Mehrfachvererbung fehlschlägt (und auch in Abwesenheit nicht garantiert funktioniert).
Natürlich ist die API nicht sehr C ++ - und selbst der "richtige" Code kann schief gehen, wenn ich von erbe
ActualType
. Ich würde also nicht behaupten, dass dies eine brillante Verwendung von istdynamic_cast<void*>
, aber es ist eine Verwendung.quelle
my_callback
könnte man sagendynamic_cast<ActualType*>(static_cast<BaseClass*>(p))
, nicht? Mit anderen Worten, wir könntenBaseClass*
als Basis für den C-Wrapper verwenden.BaseClass::get_this_for_callback
und jeder abgeleiteten Klasse die vollständige Kontrolle über das Packen des Zeigers überlassen.p
es durch einvoid*
In zurückgegeben wirdmy_callback
, warum sollte es dann wichtig sein, wennp
es als zweites Argumentregister_listener
bei Vorhandensein einer Mehrfachvererbung auf den am meisten abgeleiteten Typ umgestellt wird ? Ich frage, weil inmy_callback
dir einstatic_cast
mit einem zu tun istActualType*
, also scheint es nicht wirklich wichtig zu sein, ob ein Zeiger auf den am meisten abgeleiteten Typ oder ein Zeiger auf die Basisklasse vorbei istmy_callback
... so oder so wird es enden als Zeiger auf einActualType
Objekt, oder?void*
bis führtActualType*
nur dann zum richtigen Zeigerwert, wenn die Eingabe die Adresse einesActualType
Objekts ist. Wenn es sich alsoBaseClass
zufällig um die Basis handelt, die sich an einer anderen Adresse befindet, geht dies schief.void*
bisT*
ist nur gültig, wenn der Wert vonT*
bis kamvoid*
.Derived*
toBase*
tovoid*
toDerived*
hat undefiniertes Verhalten (könnte wahrscheinlich ohne MI funktionieren).Das Casting von Zeigern auf
void*
hat seine Bedeutung seit C Tagen. Der am besten geeignete Ort befindet sich im Speichermanager des Betriebssystems. Es muss den gesamten Zeiger und das Objekt von dem, was Sie erstellen, speichern. Indem sie es in void * speichern, verallgemeinern sie es, um jedes Objekt in der Speichermanager-Datenstruktur zu speichern, dasheap/B+Tree
einfach sein könntearraylist
.Nehmen Sie der Einfachheit halber ein Beispiel für das Erstellen eines
list
generischen Elements (Liste enthält Elemente völlig unterschiedlicher Klassen). Das wäre nur mit möglichvoid*
.Standard besagt, dass dynamic_cast für illegales Typ-Casting null zurückgeben sollte, und Standard garantiert auch, dass jeder Zeiger in der Lage sein sollte, cast in void * und zurück zu schreiben, mit Ausnahme von Funktionszeigern.
Die normale praktische Verwendung auf Anwendungsebene ist für
void*
Typografie sehr gering, wird jedoch häufig in Systemen mit niedriger Ebene / eingebetteten Systemen verwendet.Normalerweise möchten Sie reinterpret_cast für Low-Level-Inhalte verwenden, wie in 8086, um Zeiger derselben Basis zu versetzen, um die Adresse zu erhalten, ohne darauf beschränkt zu sein.
Bearbeiten: Standard sagt, dass Sie jeden Zeiger in
void*
sogar mit konvertieren können,dynamic_cast<>
aber es gibt nicht an, wo Sie angeben, dass Sie dasvoid*
Zurück nicht in das Objekt konvertieren können .Für die meisten Zwecke ist es eine Einbahnstraße, aber es gibt einige unvermeidbare Nutzungsmöglichkeiten.
Es heißt nur, dass
dynamic_cast<>
Typinformationen benötigt werden, um sie wieder in den angeforderten Typ zu konvertieren.Es gibt viele APIs, bei denen Sie
void*
an ein Objekt übergeben müssen, z. Java / Jni-Code übergibt das Objekt alsvoid*
.Ohne Typinfo können Sie das Casting nicht durchführen. Wenn Sie sicher genug sind, dass der angeforderte Typ korrekt ist , können Sie den Compiler bitten, dies
dynmaic_cast<>
mit einem Trick zu tun .Schauen Sie sich diesen Code an:
class Base_Class {public : virtual void dummy() { cout<<"Base\n";} }; class Derived_Class: public Base_Class { int a; public: void dummy() { cout<<"Derived\n";} }; class MostDerivedObject : public Derived_Class {int b; public: void dummy() { cout<<"Most\n";} }; class AnotherMostDerivedObject : public Derived_Class {int c; public: void dummy() { cout<<"AnotherMost\n";} }; int main () { try { Base_Class * ptr_a = new Derived_Class; Base_Class * ptr_b = new MostDerivedObject; Derived_Class * ptr_c,*ptr_d; ptr_c = dynamic_cast< Derived_Class *>(ptr_a); ptr_d = dynamic_cast< Derived_Class *>(ptr_b); void* testDerived = dynamic_cast<void*>(ptr_c); void* testMost = dynamic_cast<void*>(ptr_d); Base_Class* tptrDerived = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testDerived)); tptrDerived->dummy(); Base_Class* tptrMost = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testMost)); tptrMost->dummy(); //tptrMost = dynamic_cast<AnotherMostDerivedObject*>(static_cast<Base_Class*>(testMost)); //tptrMost->dummy(); //fails } catch (exception& my_ex) {cout << "Exception: " << my_ex.what();} system("pause"); return 0; }
Bitte korrigieren Sie mich, wenn dies in keiner Weise korrekt ist.
quelle
dynamic_cast
auf einenvoid*
nach dem C ++ Standard Abschnitt 5.2.7 / 2 ... Also mitdynamic_cast
dem Typ eines erholenvoid*
in einer generischen Datenstruktur wird nicht funktionieren, auch wenn diesevoid*
durch einen erzeugt wurdedynamic_cast<void*>
Betrieb. Der Zeiger, für den die Umwandlung ausgeführt wird, muss ein Zeiger auf einen vollständigen Klassentyp sein.dynamic_cast
auf einen Zeiger auf eine polymorphe Klasse.dynamic_cast<>
ist es eine Einbahnstraße, aber Sie können das Objekt von ihr zurückbekommen, wenn Sie sich über die Typinformationen des Objekts sicher sind. Abgesehen davon gibt es keine tragbare Verwendung ohne Typinformationen außer dem Vergleichen der Zeiger. Es ist jedoch als Sprachfunktion verfügbar, da dies möglich ist. Die Sprache kann Ihnen nur die Verwendung garantieren. Das gleiche gilt fürreinterpret_cast<>
.Es ist nützlich, wenn wir den Speicher wieder in den Speicherpool stellen, aber nur einen Zeiger auf die Basisklasse behalten. In diesem Fall sollten wir die ursprüngliche Adresse herausfinden.
quelle
p->~T();
gefolgt von einer neuen Platzierung über eine angedeutete Fabrik wieT::create_inplace(dynamic_cast<void*>(copy_of_p));
.Die Antwort von @ BruceAdi wird erweitert und von dieser Diskussion inspiriert . Hier ist eine polymorphe Situation, die möglicherweise eine Zeigeranpassung erfordert. Angenommen, wir haben dieses werkseitige Setup:
struct Base { virtual ~Base() = default; /* ... */ }; struct Derived : Base { /* ... */ }; template <typename ...Args> Base * Factory(Args &&... args) { return ::new Derived(std::forward<Args>(args)...); } template <typename ...Args> Base * InplaceFactory(void * location, Args &&... args) { return ::new (location) Derived(std::forward<Args>(args)...); }
Jetzt könnte ich sagen:
Aber wie würde ich das manuell bereinigen? Ich benötige die tatsächliche Speicheradresse, um Folgendes aufzurufen
::operator delete
:void * addr = dynamic_cast<void*>(p); p->~Base(); // OK thanks to virtual destructor // ::operator delete(p); // Error, wrong address! ::operator delete(addr); // OK
Oder ich könnte den Speicher wiederverwenden:
void * addr = dynamic_cast<void*>(p); p->~Base(); p = InplaceFactory(addr, "some", "arguments"); delete p; // OK now
quelle
dynamic_cast
nach dem Aufruf von destructor ist möglicherweise keine gute Idee.::operator delete
Ergebnis einer dynamischen Umwandlung. Zum Glück ist es leicht zu beheben.Mach das nicht zu Hause
struct Base { virtual ~Base (); }; struct D : Base {}; Base *create () { D *p = new D; return p; } void *destroy1 (Base *b) { void *p = dynamic_cast<void*> (b); b->~Base (); return p; } void destroy2 (void *p) { operator delete (p); } int i = (destroy2 (destroy1 (create ())), i);
Warnung : Dies funktioniert nicht , wenn
D
Folgendes definiert ist:und es gibt keine Möglichkeit, es zum Laufen zu bringen.
quelle
Dies kann eine Möglichkeit sein, einen undurchsichtigen Zeiger über einen ABI bereitzustellen . Undurchsichtige Zeiger - und allgemeiner undurchsichtige Datentypen - werden verwendet, um Objekte und andere Ressourcen zwischen Bibliothekscode und Clientcode so weiterzugeben, dass der Clientcode von den Implementierungsdetails der Bibliothek isoliert werden kann. Es gibt natürlich auch andere Möglichkeiten , dies zu erreichen, und vielleicht wären einige davon für einen bestimmten Anwendungsfall besser.
Windows verwendet in seiner API häufig opake Zeiger.
HANDLE
ist meiner Meinung nach im Allgemeinen ein undurchsichtiger Zeiger auf die tatsächliche Ressource, auf die SieHANDLE
beispielsweise zugreifen können.HANDLE
s können Kernel-Objekte wie Dateien, GDI-Objekte und alle Arten von Benutzerobjekten verschiedener Art sein, die sich in der Implementierung stark unterscheiden müssen, aber alle alsHANDLE
an den Benutzer zurückgegeben werden.#include <iostream> #include <string> #include <iomanip> using namespace std; /*** LIBRARY.H ***/ namespace lib { typedef void* MYHANDLE; void ShowObject(MYHANDLE h); MYHANDLE CreateObject(); void DestroyObject(MYHANDLE); }; /*** CLIENT CODE ***/ int main() { for( int i = 0; i < 25; ++i ) { cout << "[" << setw(2) << i << "] :"; lib::MYHANDLE h = lib::CreateObject(); lib::ShowObject(h); lib::DestroyObject(h); cout << "\n"; } } /*** LIBRARY.CPP ***/ namespace impl { class Base { public: virtual ~Base() { cout << "[~Base]"; } }; class Foo : public Base { public: virtual ~Foo() { cout << "[~Foo]"; } }; class Bar : public Base { public: virtual ~Bar() { cout << "[~Bar]"; } }; }; lib::MYHANDLE lib::CreateObject() { static bool init = false; if( !init ) { srand((unsigned)time(0)); init = true; } if( rand() % 2 ) return static_cast<impl::Base*>(new impl::Foo); else return static_cast<impl::Base*>(new impl::Bar); } void lib::DestroyObject(lib::MYHANDLE h) { delete static_cast<impl::Base*>(h); } void lib::ShowObject(lib::MYHANDLE h) { impl::Foo* foo = dynamic_cast<impl::Foo*>(static_cast<impl::Base*>(h)); impl::Bar* bar = dynamic_cast<impl::Bar*>(static_cast<impl::Base*>(h)); if( foo ) cout << "FOO"; if( bar ) cout << "BAR"; }
quelle
static_cast<Base*>(dynamic_cast<void*>(pointer_to_base))
das richtig ist? Zum Beispiel kann der resultierende Zeiger nicht mehr dynamisch in einen ungültigen Zeiger umgewandelt werden, und tatsächlich ist er überhaupt nicht mehr verwendbar.dynamic_cast<void*>(new impl::Foo)
Umarmung?Base * p;
, die auf ein abgeleitetes Objekt verweistx
,static_cast<Base*>(dynamic_cast<void*>(p))
ist dies dasselbe wiereinterpret_cast<Base*>(&x)
und nichtstatic_cast<Base*>(&x)
.dynamic_cast
dieBase*
bis zu (unten lol?)Derived*
, Und dann bekommen einvoid*