Як перевірити, чи тип об’єкта є певним підкласом у C ++?


82

Я думав про те, як використовувати, typeid()але я не знаю, як запитати, чи є цей тип підкласом іншого класу (який, до речі, абстрактний)


Мені просто цікаво, чи є спосіб перевірити, чи є тип об'єкта певним підкласом під час компіляції в C ++, оскільки std::is_base_ofвін не працюватиме за бажанням. : 3
KaiserKatze

Відповіді:


38

Ти справді не повинен. Якщо вашій програмі потрібно знати, якого класу є об'єкт, це зазвичай вказує на недолік дизайну. Подивіться, чи можете ви отримати потрібну поведінку за допомогою віртуальних функцій. Крім того, допомогло б більше інформації про те, що ви намагаєтесь зробити.

Я припускаю, що у вас така ситуація:

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

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

Якщо це те, що у вас є, спробуйте зробити щось подібне:

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

Редагувати: Оскільки суперечки щодо цієї відповіді все ще тривають через стільки років, я подумав, що слід додати деякі посилання. Якщо у вас є вказівник або посилання на базовий клас, і ваш код повинен знати похідний клас об’єкта, то це порушує принцип заміщення Лісковим . Дядько Боб називає це " анафемою об'єктно-орієнтованого проектування ".


20
+1. Я думаю, що правильна назва цього - "Скажи, не питай". По суті, завжди віддайте перевагу поліморфізму (РОЗПОВІДІТЬ об’єкту, що робити, дозволяючи реалізації подбати про це) над заявою case / if, де ви ПИТАНЕ, щоб з’ясувати, з яким типом об’єкта ви маєте справу.
LeopardSkinPillBoxHat

63
так - це все добре - але хлопець хотів знати про те, як вирішити тип
JohnIdol

7
@Dima, а що, якщо хтось хоче знати синтаксис лише для навчальних цілей (припустимо, вони переглядають книгу, написану на Java, про вади дизайну, і їм потрібно перекласти це на C ++)?
печворк

8
@Dima Коли-небудь працював із зовнішньою бібліотекою, яка визначає суперкласи? Спробуйте застосувати свою відповідь там, будь ласка.
Томаш Зато - відновити Моніку

13
Ця відповідь робить досить величезне припущення, що ви маєте контроль над типами, до яких вам потрібно передати, і можете їх переписати ... Наприклад, я додаю функцію до бібліотеки графічного інтерфейсу на основі іншої бібліотеки графічного інтерфейсу, і мені потрібно знати, чи батьківський елемент віджета можна прокручувати. Оригінальна бібліотека не пропонує жодного способу перевірити це, тому я повинен спробувати передати свої віджети батьків у базовий клас прокручуваних віджетів, що насправді відмовно. У будь-якому випадку суть у тому, що ви залишили фактичну відповідь на питання, яке зараз порушено.
AnorZaken

125

 

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

1
Вам справді потрібен dynamic_cast<>тут? Хіба цього не static_cast<>вистачить?
krlmlr

15
@krlmlr. Чи можете ви сказати тип xпід час компіляції? Якщо так, тоді static_cast<>()це спрацює. Якщо ви не можете визначити тип xдо часу виконання, тоді вам потрібноdynamic_cast<>()
Мартін Йорк

Дякую. Я використовую понижені передачі переважно в CRTP, я постійно забуваю про інші випадки використання ;-)
krlmlr

Гарна відповідь, але тут є що зауважити. Трійковий умовний оператор вимагає, щоб його другий і третій операнди мали однаковий тип. Тож уявіть, як це може працювати для будь-кого таким чином, використовуйте замість цього if / else. Можливо, це спрацьовувало раніше? Як би там не було.
Нікос

@Nikos, Це працює, тому що: 1. C ++ не вимагає, щоб тернарні випадки були однаковими, 2. Вони є типом похідного вказівника класу, і імплікація похідного класу вказує на базу.
hazer_hazer

30

Ви можете це зробити dynamic_cast(принаймні для поліморфних типів).

Насправді, якщо задуматися - ви не можете сказати, чи конкретно це конкретний тип за допомогою dynamic_cast- але ви можете сказати, чи це тип, чи будь-який його підклас.

template <class DstType, class SrcType>
bool IsType(const SrcType* src)
{
  return dynamic_cast<const DstType*>(src) != nullptr;
}

Коли підклас не є поліморфним типом?
OJFord,

6
@OllieFord: коли віртуальних функцій немає.
Дрю Холл,

Кажуть інакше, коли std::is_polymorphic_v<T>є false.
Xeverous

7

Код нижче демонструє 3 різні способи зробити це:

  • віртуальна функція
  • типовий
  • динамічний_запис
#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;
}

Програма вище друкує це:

type A
type A
type A

6

dynamic_castможе визначити, чи містить тип цільовий тип де-небудь в ієрархії успадкування (так, це маловідома особливість, яка, якщо Bуспадковує від Aта C, може перетворити A*безпосередньо на a C*). typeid()може визначити точний тип об’єкта. Однак ними обом слід користуватися надзвичайно економно. Як уже зазначалося, завжди слід уникати динамічної ідентифікації типу, оскільки це вказує на недолік конструкції. (також, якщо ви знаєте, що об'єкт точно відповідає цільовому типу, ви можете зробити пониження за допомогою a static_cast. Boost пропонує таке, polymorphic_downcastщо зробить пониження за допомогою dynamic_castі assertв режимі налагодження, а в режимі випуску він просто використовуватиме a static_cast).


4

Я не згоден з тим, що вам ніколи не слід перевіряти тип об'єкта в C ++. Якщо ви можете цього уникнути, я погоджуюсь, що вам слід. Сказання, що ви НІКОЛИ не повинні робити цього за будь-яких обставин, заходить занадто далеко. Ви можете робити це на багатьох мовах, і це може значно полегшити ваше життя. Говард Пінслі, наприклад, показав нам це у своєму дописі на C #.

Я багато працюю з Qt Framework. Загалом, я моделюю те, що я роблю, як вони роблять речі (принаймні, працюючи в їх рамках). Клас QObject є базовим класом усіх об'єктів Qt. Цей клас має функції isWidgetType () та isWindowType () як швидку перевірку підкласу. То чому б не мати можливості перевірити власні похідні класи, які порівнянні за своєю природою? Ось випуск QObject з деяких з цих інших постів:

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

А потім, коли ви передаєте вказівник на QObject, ви можете перевірити, чи вказує він на ваш похідний клас, викликаючи функцію статичного члена:

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

4

Я не знаю, чи правильно я розумію вашу проблему, тому дозвольте повторити це своїми словами ...

Проблема: Дано класи Bта Dвизначено, чи Dє підкласом B(або навпаки?)

Рішення: Використовуйте магію шаблону! Добре, серйозно вам потрібно поглянути на LOKI, чудову бібліотеку метапрограмування шаблонів, вироблену легендарним автором C ++ Андрієм Александреску.

Більш конкретно, завантажте LOKI та включіть TypeManip.hіз нього заголовок у свій вихідний код, а потім використовуйте SuperSubclassшаблон класу наступним чином:

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

Згідно з документацією, SuperSubClass<B,D>::valueістина буде, якщо Bє загальнодоступною базою D, або якщо Bі Dє псевдонімами одного типу.

тобто або Dє підкласом Bабо Dє таким самим як B.

Сподіваюся, це допоможе.

редагувати:

Зверніть увагу, оцінка того, що SuperSubClass<B,D>::valueвідбувається під час компіляції, на відміну від деяких методів, які використовують dynamic_cast, отже, не застосовується штраф за використання цієї системи під час виконання.


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

Результат:

IS A D22
IS A D2
IS A Base

2

Я думав, як використовувати typeid()...

Ну, да, це може бути зроблено шляхом порівняння: typeid().name(). Якщо взяти вже описану ситуацію, де:

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

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

Можливою реалізацією foo(Base *p)буде:

#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
    }
}

1
Чому ви поєднуєте порівняння typeid (). Name () та typeid ()? Чому не завжди порівнюємо typeid ()?
Silicomancer

1

Ви можете зробити це лише під час компіляції за допомогою шаблонів, якщо ви не використовуєте RTTI.

Це дозволяє використовувати функцію typeid, яка видасть покажчик на структуру type_info, яка містить інформацію про тип.

Прочитайте про це у Вікіпедії


Проголосував за згадування RTTI у цьому контексті, який усі інші просто проігнорували.
ManuelSchneid3r

1

У c # ви можете просто сказати:

if (myObj is Car) {

}

8
Я відповів на це перед тим, як плакат відредагував його запитання, і вказав, який вибір мови.
Говард Пінслі,

1
Я голосую проти, не виною відповіді є те, що ОП вказав його запит.
Томаш Зато - відновити Моніку

0

Ви можете зробити це за допомогою шаблонів (або SFINAE (Помилка заміщення не є помилкою)). Приклад:

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

Вихід:

base:       true
child:      true
grandchild: true
int:        false
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.