Ermitteln des Objekttyps in C ++

147

Ich habe eine Klasse A und eine andere Klasse, die davon erbt, B. Ich überschreibe eine Funktion, die ein Objekt vom Typ A als Parameter akzeptiert, also muss ich ein A akzeptieren. Später rufe ich jedoch Funktionen auf, die nur B hat. Daher möchte ich false zurückgeben und nicht fortfahren, wenn das übergebene Objekt nicht vom Typ B ist.

Wie kann ich am besten herausfinden, welcher Typ das an meine Funktion übergebene Objekt ist?

Lemnisca
quelle

Antworten:

162

dynamic_cast sollte den Trick machen

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

Das dynamic_castSchlüsselwort wandelt ein Datum von einem Zeiger oder Referenztyp in einen anderen um und führt eine Laufzeitprüfung durch, um die Gültigkeit der Umwandlung sicherzustellen.

Wenn Sie versuchen, einen Zeiger auf einen Typ zu erstellen, der kein Typ eines tatsächlichen Objekts ist, ist das Ergebnis der Umwandlung NULL. Wenn Sie versuchen, eine Umwandlung vorzunehmen, um auf einen Typ zu verweisen, der kein Typ eines tatsächlichen Objekts ist, löst die Umwandlung eine bad_castAusnahme aus.

Stellen Sie sicher, dass in der Basisklasse mindestens eine virtuelle Funktion vorhanden ist, damit dynamic_cast funktioniert.

Wikipedia-Thema Informationen zum Laufzeit-Typ

RTTI ist nur für polymorphe Klassen verfügbar, dh sie verfügen über mindestens eine virtuelle Methode. In der Praxis ist dies keine Einschränkung, da Basisklassen über einen virtuellen Destruktor verfügen müssen, damit Objekte abgeleiteter Klassen eine ordnungsgemäße Bereinigung durchführen können, wenn sie aus einem Basiszeiger gelöscht werden.

yesraaj
quelle
1
Was meinst du damit, dass es in der Basisklasse eine virtuelle Funktion geben muss, damit dynamic_cast funktioniert. Das scheint mir zu wichtig, dass ich nur raten werde.
GiCo
3
OK hat es gefunden: RTTI (Run-Time Type Information) ist nur für polymorphe Klassen verfügbar, dh sie verfügen über mindestens eine virtuelle Methode. dynamic_cast und typeid benötigen RTTI.
GiCo
Wirft nicht dynamic_cast, wenn es nicht konvertierbar ist? Gibt es eine Möglichkeit, dies zu tun, ohne einen Wurf zu generieren?
Jww
A* aptr = dynamic_cast<A*>(ptr);// soll es nicht so sein
Mehdi Karamosly
Funktioniert dies für PODs, die auf a gegossen wurden uint8_t*? Kann ich das überprüfen uint32_t* x = dynamic_cast<uint32_t*>(p), wo pist uint8_t*? (Ich versuche einen Test für Punning-Verstöße zu finden).
JWW
157

Dynamische Besetzung ist das Beste für Ihre Beschreibung des Problems, aber ich möchte nur hinzufügen, dass Sie den Klassentyp finden können mit:

#include <typeinfo>

...
string s = typeid(YourClass).name()
Robocide
quelle
4
Gut, wenn Sie wirklich nicht wissen, was Ihr Objekt ist. Die akzeptierte Antwort setzt voraus, dass Sie dies tun.
Unludo
4
@xus Ja. Es ist Teil der Standard-Header
Amey Jah
8
Ich verstehe nicht wie. Typ-ID-Namen müssen nicht nützlich sein und sind implementierungsdefiniert.
Schuh
3
Am interessantesten hier: Die Namen von Instanzen derselben Klasse müssen nicht gleich sein. Die Typ-ID selbst muss jedoch für Instanzen derselben Klasse gleich verglichen werden, siehe stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo
1
Hinweis gcc gibt den magled Namen zurück, z 11MyClass. Zum Entwirren können Sie die ABI-Erweiterungsbibliothek in verwenden cxxabi.h. Dies gibt Ihnen, abi::__cxa_demanglewas Ihnen den richtigen Namen geben wird
David G
27

Dies wird als RTTI bezeichnet , aber Sie möchten Ihr Design hier mit ziemlicher Sicherheit überdenken, da das Finden des Typs und das Ausführen spezieller Dinge darauf Ihren Code spröder macht.

Ana Betts
quelle
4
Wahr. Leider arbeite ich an einem bestehenden Projekt, so dass ich das Design oder irgendetwas in Klasse A nicht wirklich ändern kann.
Lemnisca
11

Um vollständig zu sein, werde ich Robocide aufbauen und darauf hinweisen, dass typeides alleine verwendet werden kann, ohne name () zu verwenden:

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

Ausgabe:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false
Feuerbusch
quelle
9

Betten Sie wahrscheinlich ein ID- "Tag" in Ihre Objekte ein und verwenden Sie es, um zwischen Objekten der Klasse A und Objekten der Klasse B zu unterscheiden.

Dies zeigt jedoch einen Fehler im Design. Idealerweise sollten die Methoden in B, die A nicht hat, Teil von A sein, aber leer bleiben, und B überschreibt sie. Dies beseitigt den klassenspezifischen Code und ist eher im Sinne von OOP.

Freiraum
quelle
8

Du suchst nach dynamic_cast<B*>(pointer)

Joshua
quelle
4

Weil deine Klasse nicht polymorph ist. Versuchen:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

Jetzt BaseClasist polymorph. Ich habe die Klasse in struct geändert, da die Mitglieder einer struct standardmäßig öffentlich sind.

c64zottel
quelle
3

Ihre Beschreibung ist etwas verwirrend.

Obwohl einige C ++ - Implementierungen Mechanismen dafür haben, sollten Sie im Allgemeinen nicht nach dem Typ fragen. Stattdessen sollten Sie einen dynamic_cast für den Zeiger auf A ausführen. Dies führt dazu, dass zur Laufzeit der tatsächliche Inhalt des Zeigers auf A überprüft wird. Wenn Sie ein B haben, erhalten Sie Ihren Zeiger auf B. Andernfalls erhalten Sie eine Ausnahme oder Null.

Uri
quelle
1
Es sollte beachtet werden, dass Sie nur dann eine Ausnahme erhalten , wenn Sie eine Referenzbesetzung durchführen, die fehlschlägt. dh dynamic_cast <T &> (t). Fehlgeschlagene Zeigerumwandlungen geben NULL zurück. dh dynamic_cast <T *> (t)
AlfaZulu
Ja, ich hätte das besser klarstellen sollen. Vielen Dank. Ich wünschte, es gäbe ein Wort, das in C-Typen beschrieben wird, die eher als Referenz als als Wert dienen.
Uri
3

Wie andere angegeben haben, können Sie dynamic_cast verwenden. Wenn Sie jedoch im Allgemeinen dynamic_cast verwenden, um den Typ der abgeleiteten Klasse herauszufinden, an der Sie arbeiten, weist dies auf ein schlechtes Design hin. Wenn Sie eine Funktion überschreiben, die den Zeiger von A als Parameter verwendet, sollte sie in der Lage sein, mit den Methoden / Daten der Klasse A selbst zu arbeiten, und sollte nicht von den Daten der Klasse B abhängen. In Ihrem Fall, anstatt sie zu überschreiben Sind Sie sicher, dass die Methode, die Sie schreiben, nur mit Klasse B funktioniert, sollten Sie eine neue Methode in Klasse B schreiben.

Naveen
quelle
1

Verwenden Sie überladene Funktionen. Benötigt keine Unterstützung für dynamic_cast oder RTTI:

class A {};
class B : public A {};

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};
jmucchiello
quelle
Direkt von der ursprünglichen Frage: "Ich rufe später Funktionen auf, die nur B hat" - wie würde eine Überladung in einem solchen Fall funktionieren?
Marcin Gil
Wenn Sie Bar mit einem A anrufen, passiert kein B-Zeug. Wenn Sie Bar mit einem B aufrufen, können Methoden aufgerufen werden, die nur für B vorhanden sind. Lesen Sie die ursprüngliche Frage? Bar ist sein "Ich überschreibe eine Funktion, die ein Objekt vom Typ A als Parameter akzeptiert"
jmucchiello
7
Dies funktioniert nicht mit dynamischem Polymorphismus, den der Fragesteller vermutlich verwendet. C ++ kann keine Überladung basierend auf der Laufzeitklasse des Parameters auswählen, sondern nur basierend auf dem Typ zur Kompilierungszeit.
Steve Jessop
1

Wenn Sie auf die Boost-Bibliothek zugreifen können , benötigen Sie möglicherweise die Funktion type_id_with_cvr () , die den Datentyp bereitstellen kann , ohne die Modifikatoren const, volatile und &&& zu entfernen . Hier ist ein einfaches Beispiel in C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

Hoffe das ist nützlich.

Kehe CAI
quelle