Gibt es praktische Anwendungen für dynamisches Casting, um Zeiger zu entleeren?


73

In C ++ führt die T q = dynamic_cast<T>(p);Konstruktion eine Laufzeitumwandlung eines Zeigers pauf einen anderen Zeigertyp durch T, der in der Vererbungshierarchie des dynamischen Typs von *pangezeigt 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?


7
Nur eine Vermutung, aber konnte das nicht verwendet werden, um die Objektidentität eindeutig zu bestimmen?
Björn Pollex

@ BjörnPollex: Aber so würde p... gibt es eine Situation wo p1 == p2, aber dynamic_cast<void*>(p1) != dynamic_cast<void*>(p2)?
Kerrek SB

8
Ah, ich verstehe, was du meinst: Wir könnten haben p1 != p2, aber tatsächlich zeigen sie auf dasselbe Objekt. Ich denke, wenn wir einen Index hätten void *, wäre das sinnvoll. (Obwohl der leere Zeiger selbst nicht mehr verwendbar wäre.)
Kerrek SB

Ich erhalte die Meldung "Fehler: ptr-Typ Base * kann nicht in void * umgewandelt werden (Quelltyp ist nicht polymorph)." Wenn ich versuche, Code mit dieser Besetzung zu kompilieren, funktioniert das auf Ihrem System? Und entschuldigen Sie das Löschen des Originalkommentars - ich wollte etwas noch einmal überprüfen :)
John Humphreys - w00te

2
@ BjörnPollex: Du solltest diesen Kommentar in eine Antwort umwandeln - es klingt nach einer vernünftigen Idee, die es sicherlich wert ist, einen Beitrag zu haben.
Kerrek SB

Antworten:


69

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

Ich vergebe das Kopfgeld für diese Frage hauptsächlich, weil ich der Meinung bin, dass keine der anderen Antworten etwas zutiefst Neues hinzugefügt hat. Das war nicht die ursprüngliche Absicht, aber ich wollte das Kopfgeld nicht verschwenden lassen. Wenn jemand eine neue gute Antwort findet (oder eine vorhandene Antwort bearbeitet), starte ich gerne eine weitere "Belohnungs" -Bounty!
Kerrek SB

7

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 ist dynamic_cast<void*>, aber es ist eine Verwendung.


3
Nun, aber my_callbackkönnte man sagen dynamic_cast<ActualType*>(static_cast<BaseClass*>(p)), nicht? Mit anderen Worten, wir könnten BaseClass*als Basis für den C-Wrapper verwenden.
Kerrek SB

@ Kerrek: Ja, ich denke du könntest. In diesem Fall können Sie eine weitere virtuelle Funktion hinzufügen BaseClass::get_this_for_callbackund jeder abgeleiteten Klasse die vollständige Kontrolle über das Packen des Zeigers überlassen.
Steve Jessop

@SteveJessop: Nur neugierig, wenn pes durch ein void*In zurückgegeben wird my_callback, warum sollte es dann wichtig sein, wenn pes als zweites Argument register_listenerbei Vorhandensein einer Mehrfachvererbung auf den am meisten abgeleiteten Typ umgestellt wird ? Ich frage, weil in my_callbackdir ein static_castmit einem zu tun ist ActualType*, also scheint es nicht wirklich wichtig zu sein, ob ein Zeiger auf den am meisten abgeleiteten Typ oder ein Zeiger auf die Basisklasse vorbei ist my_callback... so oder so wird es enden als Zeiger auf ein ActualTypeObjekt, oder?
Jason

@ Jason: nein. Bei Mehrfachvererbung und unter der Annahme, dass einige Basen nicht leer sind, unterscheidet sich die Adresse mindestens einer der Basen von der Adresse des am meisten abgeleiteten Objekts. Die statische Umwandlung von void*bis führt ActualType*nur dann zum richtigen Zeigerwert, wenn die Eingabe die Adresse eines ActualTypeObjekts ist. Wenn es sich also BaseClasszufällig um die Basis handelt, die sich an einer anderen Adresse befindet, geht dies schief.
Steve Jessop

@Jason Auch ohne MI: Casting von void*bis T*ist nur gültig, wenn der Wert von T*bis kam void*. Derived*to Base*to void*to Derived*hat undefiniertes Verhalten (könnte wahrscheinlich ohne MI funktionieren).
Neugieriger

4

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, das heap/B+Treeeinfach sein könnte arraylist.

Nehmen Sie der Einfachheit halber ein Beispiel für das Erstellen eines listgenerischen Elements (Liste enthält Elemente völlig unterschiedlicher Klassen). Das wäre nur mit möglich void*.

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 das void*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 als void*.
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.


+1 hast du ein paar Links dazu, da ich an solchen "Low Level Stuff" interessiert bin
Sim

code.google.com/p/c-generic-library Dieser Link enthält Code für die generische Datenstruktur.
Praveen

2
Sie können nicht tun , dynamic_castauf einen void*nach dem C ++ Standard Abschnitt 5.2.7 / 2 ... Also mit dynamic_castdem Typ eines erholen void*in einer generischen Datenstruktur wird nicht funktionieren, auch wenn diese void*durch einen erzeugt wurde dynamic_cast<void*>Betrieb. Der Zeiger, für den die Umwandlung ausgeführt wird, muss ein Zeiger auf einen vollständigen Klassentyp sein.
Jason

Tatsächlich. Ich fürchte, diese Antwort geht etwas daneben. Mir sind allgemeine Leerenzeiger bekannt. Die Frage bezieht sich jedoch speziell dynamic_castauf einen Zeiger auf eine polymorphe Klasse.
Kerrek SB

In der Tat 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ür reinterpret_cast<>.
Praveen

1

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.


Hm ... können Sie eine Situation erläutern, in der dies nützlich wäre?
Kerrek SB

Das macht für mich jetzt mehr Sinn: Sie können sagen, p->~T();gefolgt von einer neuen Platzierung über eine angedeutete Fabrik wie T::create_inplace(dynamic_cast<void*>(copy_of_p));.
Kerrek SB

1

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:

Base * p = Factory();

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

Die Verwendung dynamic_castnach dem Aufruf von destructor ist möglicherweise keine gute Idee.
Tadeusz Kopec

Vorletzter Code-Ausschnitt: Destruktor, ::operator deleteErgebnis einer dynamischen Umwandlung. Zum Glück ist es leicht zu beheben.
Tadeusz Kopec

0

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 DFolgendes definiert ist:

Struktur D: Basis {
    void * operator new (size_t);
    void operator delete (void *);
};

und es gibt keine Möglichkeit, es zum Laufen zu bringen.


@SteveJessop OK, ich habe die große Warnmeldung gesetzt.
Neugieriger

0

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. HANDLEist meiner Meinung nach im Allgemeinen ein undurchsichtiger Zeiger auf die tatsächliche Ressource, auf die Sie HANDLEbeispielsweise zugreifen können. HANDLEs 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 als HANDLEan 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";
}

Interessant - aber sind Sie sicher, dass 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.
Kerrek SB

dynamic_cast<void*>(new impl::Foo)Umarmung?
Neugieriger

@ Kerrek: Ich bin mir ziemlich sicher, dass der Code korrekt ist und keine UB aufweist. Siehe zum Beispiel: 5.2.9 / 10: "Ein Wert vom Typ" Zeiger auf cv1 void "kann in einen Wert vom Typ" Zeiger auf cv2 T "konvertiert werden, wobei T ein Objekttyp und cv2 dieselbe cv-Qualifikation ist als oder eine höhere Lebenslaufqualifikation als cv1. Ein Wert vom Typ Zeiger auf Objekt, der in "Zeiger auf Lebenslauf ungültig" und zurück zum ursprünglichen Zeigertyp konvertiert wurde, hat seinen ursprünglichen Wert. "
John Dibling

@ JohnDibling: Ich glaube, ich habe es geschafft, mit dieser Konstruktion einen Absturz zu verursachen. Stellen Sie sich vor: Wenn Sie eine haben Base * p;, die auf ein abgeleitetes Objekt verweist x, static_cast<Base*>(dynamic_cast<void*>(p))ist dies dasselbe wie reinterpret_cast<Base*>(&x)und nicht static_cast<Base*>(&x) .
Kerrek SB

@Kerrek: Right - Sie müssten dynamic_castdie Base*bis zu (unten lol?) Derived*, Und dann bekommen einvoid*
John Dibling
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.