Jak sprawdzić, czy typ obiektu jest podklasą w C++?

Myślałem o używaniu typeid() ale nie wiem, jak zapytać, czy ten typ jest podklasą innej klasy (która, nawiasem mówiąc, jest abstrakcyjna)

Author: Ziezi, 2008-11-21

12 answers

Naprawdę nie powinieneś. Jeśli twój program musi wiedzieć, jaką klasą jest obiekt, zwykle wskazuje to na wadę projektu. Sprawdź, czy możesz uzyskać pożądane zachowanie za pomocą funkcji wirtualnych. Również więcej informacji na temat tego, co próbujesz zrobić, pomoże.

Zakładam, że masz taką sytuację:

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

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

Jeśli to jest to, co masz, to spróbuj zrobić coś takiego:

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();
}

Edit: ponieważ debata na temat tej odpowiedzi wciąż trwa po tylu lat, pomyślałem, że powinienem dorzucić jakieś referencje. Jeśli masz wskaźnik lub odniesienie do klasy bazowej, a Twój kod musi znać klasę pochodną obiektu, to narusza ona zasadę podstawienia Liskov . Wujek Bob nazywa to " anathema to Object Oriented Design ".

 38
Author: Dima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-16 18:31:45

 

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;
  }
}
 125
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-26 09:56:03

Możesz to zrobić za pomocą dynamic_cast (przynajmniej dla typów polimorficznych).

Właściwie, po namyśle -- nie możesz powiedzieć, czy jest to konkretny typ z dynamic_cast--ale możesz powiedzieć, czy jest to ten typ lub jakaś jego podklasa.

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}
 30
Author: Drew Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-26 09:56:14

Poniższy kod pokazuje 3 różne sposoby zrobienia tego:

  • funkcja wirtualna
  • typeid
  • 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;
}

Powyższy program wypisuje to:

type A
type A
type A
 7
Author: ajneu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-10 19:39:58

dynamic_cast może określić, czy typ zawiera typ docelowy w dowolnym miejscu w hierarchii dziedziczenia (tak, jest to mało znana cecha, że jeśli B dziedziczy z A i C, może przekształcić A* bezpośrednio w C*). typeid() może określić dokładny typ obiektu. Należy je jednak stosować wyjątkowo oszczędnie. Jak już wspomniano, zawsze należy unikać dynamicznej identyfikacji typu, ponieważ wskazuje to na wadę projektu. (również, jeśli wiesz, że obiekt jest dla pewien typ celu, możesz zrobić downcast za pomocą static_cast. Boost oferuje polymorphic_downcast, który zrobi downcast z dynamic_cast i assert w trybie debugowania, a w trybie release użyje static_cast).

 6
Author: coppro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 04:49:14

Nie zgadzam się, że nigdy nie powinieneś sprawdzać typu obiektu w C++. Jeśli możesz tego uniknąć, zgadzam się, że powinieneś. Mówienie, że nigdy nie powinieneś tego robić pod żadnym pozorem, posuwa się jednak za daleko. Możesz to zrobić w wielu językach, a to może znacznie ułatwić Ci życie. Howard Pinsley, na przykład, pokazał nam jak w swoim poście na C#.

Wykonuję dużo pracy z frameworkiem Qt. Ogólnie rzecz biorąc, modeluję to, co robię po tym, jak robią rzeczy (przynajmniej podczas pracy w ich ramy). Klasa QObject jest klasą bazową wszystkich obiektów Qt. Ta klasa ma funkcje isWidgetType () i isWindowType () jako szybkie sprawdzanie podklasy. Dlaczego więc nie być w stanie sprawdzić własnych klas pochodnych, które są porównywalne w swojej naturze? Oto QObject spin off niektórych z tych innych postów:

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 ); }
};

I wtedy, gdy przekazujesz wskaźnik do obiektu QObject, możesz sprawdzić, czy wskazuje on na Twoją klasę pochodną, wywołując statyczną funkcję członka:

if( MyQObject::isThisType( qObjPtr ) ) qDebug() << "This is a MyQObject!";
 4
Author: BuvinJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-19 21:31:12

Nie wiem, czy dobrze rozumiem twój problem, więc powtórzę to własnymi słowami...

Problem: podane klasy B i D określają czy D jest podklasą B (czy odwrotnie?)

Rozwiązanie: Użyj Magii szablonów! Ok, poważnie musisz spojrzeć na LOKI, doskonałą bibliotekę meta-programowania szablonów stworzoną przez legendarnego autora C++ Andrei Alexandrescu.

Dokładniej, Pobierz LOKI i dołącz nagłówek TypeManip.h z niego w kodzie źródłowym użyj szablonu klasy SuperSubclass w następujący sposób:

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

Zgodnie z dokumentacją, SuperSubClass<B,D>::value będzie prawdą, jeśli B jest publiczną bazą D, lub jeśli B i D są aliasami tego samego typu.

Czyli D jest podklasą B lub D jest tym samym co B.

Mam nadzieję, że to pomoże.

Edit:

Zwróć uwagę, że ocena SuperSubClass<B,D>::value odbywa się w czasie kompilacji, w przeciwieństwie do niektórych metod, które używają dynamic_cast, stąd nie ma kary za używanie tego systemu w czasie wykonywania.

 4
Author: Autodidact,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-13 15:46:27
#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;
  }
}

Wynik:

IS A D22
IS A D2
IS A Base
 3
Author: Reinaldo Guedes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-26 17:00:59

Myślałem o używaniu typeid()...

Tak, można to zrobić porównując: typeid().name(). Jeśli weźmiemy już opisaną sytuację, gdzie:
class Base;
class A : public Base {...};
class B : public Base {...};

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

Możliwa implementacja foo(Base *p) to:

#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
    }
}
 2
Author: Ziezi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-06 22:40:56

Możesz to zrobić tylko w czasie kompilacji używając szablonów, chyba że używasz RTTI.

Pozwala na użycie funkcji typeid, która da wskaźnik do struktury type_info, która zawiera informacje o typie.

Przeczytaj o tym na Wikipedia

 1
Author: user32141,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 03:55:55

W c# można po prostu powiedzieć:

if (myObj is Car) {

}
 1
Author: Howard Pinsley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-01 17:58:47

Możesz to zrobić za pomocą szablonów (lub SFINAE (błąd substytucji nie jest błędem)). Przykład:

#include <iostream>

class base
{
public:
    virtual ~base() = default;
};

template <
    class type,
    class = decltype(
        static_cast<base*>(static_cast<type*>(0))
    )
>
bool check(type)
{
    return true;
}

bool check(...)
{
    return false;
}

class child : public base
{
public:
    virtual ~child() = default;
};

class grandchild : public child {};

int main()
{
    std::cout << std::boolalpha;

    std::cout << "base:       " << check(base())       << '\n';
    std::cout << "child:      " << check(child())      << '\n';
    std::cout << "grandchild: " << check(grandchild()) << '\n';
    std::cout << "int:        " << check(int())        << '\n';

    std::cout << std::flush;
}

Wyjście:

base:       true
child:      true
grandchild: true
int:        false
 0
Author: Akib Azmain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-20 13:16:26