Wie überprüfe ich, ob der Typ eines Objekts eine bestimmte Unterklasse in C ++ ist?

79

Ich habe typeid()nach dem Prinzip der Verwendung gedacht, aber ich weiß nicht, wie ich fragen soll, ob dieser Typ eine Unterklasse einer anderen Klasse ist (die übrigens abstrakt ist).

Tschad
quelle
Ich frage mich nur, ob es eine Möglichkeit gibt, zu überprüfen, ob der Typ eines Objekts zur Kompilierungszeit in C ++ eine bestimmte Unterklasse ist , da dies std::is_base_ofnicht wie gewünscht funktioniert. : 3
KaiserKatze

Antworten:

38

Das solltest du wirklich nicht. Wenn Ihr Programm wissen muss, um welche Klasse es sich bei einem Objekt handelt, weist dies normalerweise auf einen Konstruktionsfehler hin. Überprüfen Sie, ob Sie mithilfe virtueller Funktionen das gewünschte Verhalten erzielen können. Außerdem würden weitere Informationen darüber, was Sie versuchen, helfen.

Ich gehe davon aus, dass Sie eine Situation wie diese haben:

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

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

Wenn Sie dies haben, versuchen Sie Folgendes:

class Base
{
  virtual void bar() = 0;
};

class A : public Base
{
  void bar() {/* do X */}
};

class B : public Base
{
  void bar() {/* do Y */}
};

void foo(Base *p)
{
  p->bar();
}

Bearbeiten: Da die Debatte über diese Antwort nach so vielen Jahren immer noch andauert, dachte ich, ich sollte einige Referenzen einbringen. Wenn Sie einen Zeiger oder Verweis auf eine Basisklasse haben und Ihr Code die abgeleitete Klasse des Objekts kennen muss, verstößt er gegen das Liskov-Substitutionsprinzip . Onkel Bob nennt dies ein " Anathema für objektorientiertes Design ".

Dima
quelle
20
+1. Ich denke, der richtige Name dafür ist "Tell, frag nicht". Bevorzugen Sie grundsätzlich immer den Polymorphismus (einem Objekt sagen, was zu tun ist, damit sich die Implementierung darum kümmert) gegenüber einer case / if-Anweisung, bei der Sie nachfragen, um herauszufinden, mit welchem ​​Objekttyp Sie es zu tun haben.
LeopardSkinPillBoxHat
60
Ja - das ist alles gut - aber der Typ wollte wissen, wie man den Typ
auflöst
7
@Dima, und was ist, wenn jemand die Syntax nur zu Lernzwecken kennen möchte (sagen wir, er geht ein in Java geschriebenes Buch über Designfehler durch und muss das in C ++ übersetzen)?
Patchwork
8
@Dima Haben Sie jemals mit einer externen Bibliothek gearbeitet, die Superklassen definiert? Versuchen Sie bitte, Ihre Antwort dort anzuwenden.
Tomáš Zato - Wiedereinsetzung Monica
12
Diese Antwort macht die eher große Annahme , dass Sie die Kontrolle über die Typen haben Sie zu Guss müssen und können sie neu schreiben ... Zum Beispiel habe ich eine Funktion , um eine GUI - Bibliothek bin das Hinzufügen einer anderen GUI - Bibliothek basiert, und ich muss wissen , ob Das übergeordnete Element eines Widgets kann gescrollt werden. Die Originalbibliothek bietet keine Möglichkeit, dies zu testen, daher muss ich versuchen, meine übergeordneten Widgets in die Basisklasse der scrollbaren Widgets umzuwandeln, was wirklich scheiße ist. Der Punkt ist jedenfalls, dass Sie die eigentliche Antwort auf die vorliegende Frage ausgelassen haben .
AnorZaken
123

 

class Base
{
  public: virtual ~Base() {}
};

class D1: public Base {};

class D2: public Base {};

int main(int argc,char* argv[]);
{
  D1   d1;
  D2   d2;

  Base*  x = (argc > 2)?&d1:&d2;

  if (dynamic_cast<D2*>(x) == nullptr)
  {
    std::cout << "NOT A D2" << std::endl;
  }
  if (dynamic_cast<D1*>(x) == nullptr)
  {
    std::cout << "NOT A D1" << std::endl;
  }
}
Martin York
quelle
1
Benötigen Sie wirklich eine dynamic_cast<>hier? Wäre das nicht static_cast<>genug?
krlmlr
15
@krlmlr. Können Sie den Typ xbeim Kompilieren angeben? Wenn ja, dann static_cast<>()würde es funktionieren. Wenn Sie die Art der xbis zur Laufzeit nicht sagen können, dann brauchen Siedynamic_cast<>()
Martin York
Vielen Dank. Ich benutze Downcasts hauptsächlich im CRTP, ich vergesse immer wieder andere Anwendungsfälle
;-)
Gute Antwort, aber hier etwas zu beachten. Der ternäre bedingte Operator erfordert, dass sein zweiter und dritter Operand denselben Typ haben. Daher idk, wie dies für jeden auf diese Weise funktionieren kann, verwenden Sie stattdessen ein if / else. Vielleicht hat das in der Vergangenheit funktioniert? Jedenfalls.
Nikos
@Nikos, es funktioniert, weil: 1. C ++ nicht erfordert, dass ternäre Fälle vom gleichen Typ sind, 2. sie sind Typ des abgeleiteten Klassenzeigers und abgeleitete Klassenzeiger-Implizitätsumwandlungen zur Basis.
hazer_hazer
30

Sie können es mit tun dynamic_cast(zumindest für polymorphe Typen).

Beim zweiten Gedanken - Sie können nicht sagen, ob es sich SPEZIELL um einen bestimmten Typ dynamic_casthandelt - können Sie jedoch feststellen, ob es sich um diesen Typ oder eine Unterklasse davon handelt.

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}
Drew Hall
quelle
Wann ist eine Unterklasse kein polymorpher Typ?
OJFord
6
@OllieFord: Wenn keine virtuellen Funktionen vorhanden sind.
Drew Hall
Anders gesagt, wann std::is_polymorphic_v<T>ist false.
Xeverous
7

Der folgende Code zeigt drei verschiedene Möglichkeiten:

  • virtuelle Funktion
  • Typid
  • dynamic_cast
#include <iostream>
#include <typeinfo>
#include <typeindex>

enum class Type {Base, A, B};

class Base {
public:
    virtual ~Base() = default;
    virtual Type type() const {
        return Type::Base;
    }
};

class A : public Base {
    Type type() const override {
        return Type::A;
    }
};

class B : public Base {
    Type type() const override {
        return Type::B;
    }
};

int main()
{
    const char *typemsg;
    A a;
    B b;
    Base *base = &a;             // = &b;    !!!!!!!!!!!!!!!!!
    Base &bbb = *base;

    // below you can replace    base    with  &bbb    and get the same results

    // USING virtual function
    // ======================
    // classes need to be in your control
    switch(base->type()) {
    case Type::A:
        typemsg = "type A";
        break;
    case Type::B:
        typemsg = "type B";
        break;
    default:
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING typeid
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    std::type_index ti(typeid(*base));
    if (ti == std::type_index(typeid(A))) {
        typemsg = "type A";
    } else if (ti == std::type_index(typeid(B))) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;

    // USING dynamic_cast
    // ======================
    // needs RTTI. under gcc, avoid -fno-rtti
    if (dynamic_cast</*const*/ A*>(base)) {
        typemsg = "type A";
    } else if (dynamic_cast</*const*/ B*>(base)) {
        typemsg = "type B";
    } else {
        typemsg = "unknown";
    }
    std::cout << typemsg << std::endl;
}

Das obige Programm druckt Folgendes:

type A
type A
type A
Ajneu
quelle
6

dynamic_castkann bestimmen, ob der Typ den Zieltyp irgendwo in der Vererbungshierarchie enthält (ja, es ist eine wenig bekannte Funktion, die, wenn er Bvon Aund erbt C, einen A*direkt in einen verwandeln kann C*). typeid()kann den genauen Typ des Objekts bestimmen. Beide sollten jedoch äußerst sparsam eingesetzt werden. Wie bereits erwähnt, sollten Sie die dynamische Typidentifikation immer vermeiden, da dies auf einen Konstruktionsfehler hinweist. (Wenn Sie wissen, dass das Objekt sicher vom Zieltyp ist, können Sie einen Downcast mit a static_castausführen. Boost bietet polymorphic_downcasteinen Downcast mit dynamic_castund assertim Debug-Modus und im Release-Modus wird nur a verwendet. static_cast)

coppro
quelle
4

Ich bin nicht der Meinung, dass Sie niemals den Typ eines Objekts in C ++ überprüfen möchten. Wenn Sie es vermeiden können, stimme ich zu, dass Sie sollten. Zu sagen, dass Sie dies NIEMALS tun sollten, geht jedoch zu weit. Sie können dies in sehr vielen Sprachen tun, und es kann Ihr Leben viel einfacher machen. Howard Pinsley zum Beispiel hat uns in seinem Beitrag auf C # gezeigt, wie es geht.

Ich arbeite viel mit dem Qt Framework. Im Allgemeinen modelliere ich meine Arbeit nach der Art und Weise, wie sie Dinge tun (zumindest wenn ich in ihrem Rahmen arbeite). Die QObject-Klasse ist die Basisklasse aller Qt-Objekte. Diese Klasse hat die Funktionen isWidgetType () und isWindowType () als schnelle Unterklassenprüfung. Warum also nicht in der Lage sein, Ihre eigenen abgeleiteten Klassen zu überprüfen, was seiner Natur nach vergleichbar ist? Hier ist ein QObject-Spin-off einiger dieser anderen Beiträge:

class MyQObject : public QObject
{
public:
    MyQObject( QObject *parent = 0 ) : QObject( parent ){}
    ~MyQObject(){}

    static bool isThisType( const QObject *qObj )
    { return ( dynamic_cast<const MyQObject*>(qObj) != NULL ); }
};

Wenn Sie dann einen Zeiger auf ein QObject übergeben, können Sie überprüfen, ob er auf Ihre abgeleitete Klasse verweist, indem Sie die statische Elementfunktion aufrufen:

if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";
BuvinJ
quelle
4

Ich weiß nicht, ob ich Ihr Problem richtig verstehe, also lassen Sie es mich in meinen eigenen Worten wiederholen ...

Problem: Geben Sie bei gegebenen Klassen Bund an D, ob Des sich um eine Unterklasse von handelt B(oder umgekehrt?).

Lösung: Verwenden Sie etwas Vorlagenmagie! Okay, im Ernst, Sie müssen sich LOKI ansehen, eine hervorragende Meta-Programmierbibliothek für Vorlagen, die vom legendären C ++ - Autor Andrei Alexandrescu erstellt wurde.

Laden Sie LOKI herunter und fügen Sie den Header TypeManip.hdaraus in Ihren Quellcode ein. Verwenden Sie dann die SuperSubclassKlassenvorlage wie folgt:

if(SuperSubClass<B,D>::value)
{
...
}

Laut Dokumentation SuperSubClass<B,D>::valuegilt, ob Bes sich um eine öffentliche Basis Dhandelt oder ob Bund DAliase des gleichen Typs sind.

dh entweder Dist eine Unterklasse von Boder Dist die gleiche wie B.

Ich hoffe das hilft.

bearbeiten:

Bitte beachten Sie, dass die Auswertung von SuperSubClass<B,D>::valuezur Kompilierungszeit erfolgt, im Gegensatz zu einigen Methoden, die verwendet dynamic_castwerden. Daher gibt es keine Strafe für die Verwendung dieses Systems zur Laufzeit.

Autodidakt
quelle
3
#include <stdio.h>
#include <iostream.h>

class Base
{
  public: virtual ~Base() {}

  template<typename T>
  bool isA() {
    return (dynamic_cast<T*>(this) != NULL);
  }
};

class D1: public Base {};
class D2: public Base {};
class D22: public D2 {};

int main(int argc,char* argv[]);
{
  D1*   d1  = new D1();
  D2*   d2  = new D2();
  D22*  d22 = new D22();

  Base*  x = d22;

  if( x->isA<D22>() )
  {
    std::cout << "IS A D22" << std::endl;
  }
  if( x->isA<D2>() )
  {
    std::cout << "IS A D2" << std::endl;
  }
  if( x->isA<D1>() )
  {
    std::cout << "IS A D1" << std::endl;
  }
  if(x->isA<Base>() )
  {
    std::cout << "IS A Base" << std::endl;
  }
}

Ergebnis:

IS A D22
IS A D2
IS A Base
Reinaldo Guedes
quelle
1

Sie können dies nur zur Kompilierungszeit mithilfe von Vorlagen tun, es sei denn, Sie verwenden RTTI.

Hiermit können Sie die Funktion typeid verwenden, die einen Zeiger auf eine type_info-Struktur liefert, die Informationen zum Typ enthält.

Lesen Sie es bei Wikipedia nach

user32141
quelle
Gewählt für die Erwähnung von RTTI in diesem Zusammenhang, die alle anderen einfach ignoriert haben.
ManuelSchneid3r
1

In c # kann man einfach sagen:

if (myObj is Car) {

}
Howard Pinsley
quelle
8
Ich habe dies beantwortet, bevor das Poster seine Frage bearbeitet und seine Sprachwahl angegeben hat.
Howard Pinsley
1
Ich stimme zu, es ist nicht die Schuld der Antwort, dass das OP seine Anfrage spezifiziert hat.
Tomáš Zato - Wiedereinsetzung Monica
1

Ich dachte an die Verwendung von typeid()...

Nun ja, es könnte durch Vergleichen von : typeid().name(). Wenn wir die bereits beschriebene Situation annehmen, wo:

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

void foo(Base *p)
{
  if(/* p is A */) /* do X */
  else /* do Y */
}

Eine mögliche Implementierung von foo(Base *p)wäre:

#include <typeinfo>

void foo(Base *p)
{
    if(typeid(*p) == typeid(A))
    {
        // the pointer is pointing to the derived class A
    }  
    else if (typeid(*p).name() == typeid(B).name()) 
    {
        // the pointer is pointing to the derived class B
    }
}
Ziezi
quelle
1
Warum mischen Sie den Vergleich von typeid (). Name () und typeid ()? Warum nicht immer typeid () vergleichen?
Silicomancer