Есть ли способ определить тип объекта?

Это может быть глупый вопрос, я подозреваю, что знаю ответ (нет), потому что, кажется, я упираюсь в стену.

Учитывая, что у меня есть коллекция объектов, полученных из определенного класса:

class BaseClass;
class DerivedA: public BaseClass;
class DerivedB: public BaseClass;
class DerivedC: public BaseClass;
std::vector<BaseClass> myCollection;

Я хочу вызвать метод в зависимости от типов конкретного класса:

class Processor {
  void doSomething(DerivedA a, DerivedB b);
  void doSomething(DerivedA a, DerivedC c);
}

Проблема в том, что если я получу доступ к отдельным элементам в коллекции и попытаюсь вызвать метод «doSomething» в «Процессоре», он не сможет решить, какой метод использовать (на самом деле). Итак, мой вопрос: есть ли способ получить элементы в коллекции с правильным производным типом?


person uorbe001    schedule 27.12.2012    source источник
comment
Если вам нужна поддержка деривации (и связанный с ней полиморфизм). ваш вектор должен состоять из указателей (и умных указателей при этом); не экземпляры базового класса.   -  person WhozCraig    schedule 27.12.2012
comment
Вас также может заинтересовать шаблон посетителя.   -  person Tom Knapen    schedule 27.12.2012
comment
@WhozCraig Спасибо за предупреждение, Картик Т. объяснил, зачем мне это нужно, и я не знал, что при использовании экземпляров произойдет нарезка объектов.   -  person uorbe001    schedule 28.12.2012
comment
Ниже приведен пример множественной отправки в C++11: ideone.com/lTsc7M   -  person Jarod42    schedule 04.02.2014


Ответы (2)


Если вы собираетесь оставить метод doSomething как есть, это то, что называется множественная отправка и в настоящее время НЕ поддерживается C++.

Если бы это была виртуальная функция-член BaseClass, то да, это был бы полиморфизм мельницы С++ для объекта, для которого он вызывается, но он все равно НЕ вывел бы автоматически тип аргумента.

Чтобы обойти это, вы можете сделать что-то вроде того, что предлагается в предыдущей ссылке.

void collideWith(Thing& other) {
     // dynamic_cast to a pointer type returns NULL if the cast fails
     // (dynamic_cast to a reference type would throw an exception on failure)
     if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
         // handle Asteroid-Asteroid collision
     } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
         // handle Asteroid-Spaceship collision
     } else {
         // default collision handling here
     }
 }

В основном продолжайте приведение к различным возможным производным классам, пока один из них не сработает и не вызовет один из методов соответствующим образом (никаких особых усилий, поскольку компилятор знает, к какому типу вы пытаетесь привести).

ВАЖНО: как указывает @WhozCraig, ваш вектор должен содержать указатели, чтобы избежать Object-Slicing и сделать весь этот вопрос спорным.

person Karthik T    schedule 27.12.2012
comment
Это не так красиво, как хотелось бы, но, похоже, это лучший вариант для того, чтобы делать то, что я хочу. Спасибо за объяснение комментария @WhozCraig, я не понимал, почему он это сказал, пока не прочитал ваш ответ :-) - person uorbe001; 28.12.2012
comment
@ uorbe001 Извините, я не очень подробно рассказал в этом комментарии, но Картик довольно хорошо заполнил дыру, которую я оставил в этом ответе. Рад, что вы видите, как это связано. - person WhozCraig; 28.12.2012

Хорошо, да, вы должны использовать полиморфизм, как указано выше. Если ваша функция должна обрабатывать 2 объекта, это становится чрезвычайно сложным.

Если производные образуют ограниченный набор и знают друг друга, вы можете использовать двойную отправку. Это не идеально, но решает этот конкретный случай.

class DerivedA;
class DerivedB;
class DerivedC;

class BaseClass
{
 public:
     virtual ~BaseClass();

     virtual void doSomethingWithBase( BaseClass & b2 ) = 0;
     virtual void doSomethingWithDerivedA( DerivedA & da ) = 0;
     virtual void doSomethingWithDerivedB( DerivedB & db ) = 0;
     virtual void doSomethingWithDerivedC( DerivedC & dc ) = 0;
};

class DerivedA : public BaseClass
{
   public:

      void doSomethingWithBase( BaseClass & b2 )
      {
           b2.doSomethingWithDerivedA( *this );
      }

      void doSomethingWithDerivedA( DerivedA & da )
      {
           // implement for two DerivedA objects
      }

      void doSomethingWithDerivedB( DerivedB & db )
      {
           // implement for an A and B
      }

      void doSomethingWithDerivedC( DerivedC & dc )
      {
          // implement for an A and C
      }
 };

 // implement DerivedB to call doSomethingWithDerivedB on its parameter
 // implement DerivedC to call doSomethingWithDerivedC on its parameter.

Вы поняли идею. Откуда вы звоните, вам не нужно знать, какие два типа у вас есть, и вам никогда не нужно искать это на самом деле. Но если вы когда-нибудь добавите больше реализаций, у вас будет много кода для редактирования, и вы можете подумать о какой-то таблице поиска.

Если вам нужен класс, чтобы определить себя, вы можете использовать какой-то виртуальный идентификатор.

  class BaseClass
  {
      public:
         virtual int id() const = 0;
  };

а затем вы заставляете классы раскрывать свои идентификаторы и находить обработчик в таблице на основе этих идентификаторов, который будет обрабатывать два объекта. Идентификаторы не обязательно должны быть целыми числами, они могут быть строками, что упрощает предотвращение конфликтов имен, и это имеет преимущество перед методом двойной отправки базового класса, который не знает своих производных классов или они знают друг друга, и быть расширяемым. Вам также не нужно обрабатывать каждую пару.

person CashCow    schedule 27.12.2012
comment
Я рассматривал двойную отправку, но это не сработает, если я хочу сохранить логику в отдельном классе (в данном случае я так и делаю). Теоретически, насколько я знаю, вы можете реализовать тройную отправку, но это вызовет больше проблем, чем пользы :-( - person uorbe001; 28.12.2012