сравнение типов во время выполнения

Мне нужно найти тип объекта, на который указывает указатель. Код приведен ниже.

//pWindow is pointer to either base Window object or derived Window objects like //Window_Derived.
const char* windowName = typeid(*pWindow).name(); 
if(strcmp(windowName, typeid(Window).name()) == 0)
{
  // ...
}
else if(strcmp(windowName, typeid(Window_Derived).name()) == 0)
{
  // ...     
}

Поскольку я не могу использовать оператор switch для сравнения строк, я вынужден использовать цепочку if else. Но поскольку количество типов окон, которые у меня есть, велико, эта цепочка if else становится слишком длинной. Можем ли мы проверить тип окна с помощью переключателя или более простого метода?

РЕДАКТИРОВАТЬ: я работаю в модуле регистратора. Я думал, что регистратор не должен вызывать виртуальную функцию производного класса для ведения журнала. Это должно сделать самостоятельно. Поэтому я отказался от подхода виртуальной функции.


person bjskishore123    schedule 19.08.2010    source источник
comment
Как правило, если вам приходится принимать решения, основанные на типе объекта, вы обнаружите, что рефакторинг вашего кода для улучшения дизайна решит эту проблему за вас.   -  person Ant    schedule 19.08.2010
comment
Вы рассматривали специализацию шаблона ??   -  person DumbCoder    schedule 19.08.2010
comment
@DumbCoder: Какое это имеет отношение к чему-либо? шаблоны предназначены для типа компиляции, и они также не принимают строки в качестве параметров.   -  person the_drow    schedule 19.08.2010
comment
@the_drow Когда я упоминал об использовании строк?   -  person DumbCoder    schedule 19.08.2010
comment
@DumbCoder: Затем использовать тип, который известен только во время выполнения? Что именно вы имели в виду.   -  person the_drow    schedule 19.08.2010
comment
@bjskishore123: Что касается вашего редактирования. Это потому, что вы считаете, что ведение журнала не относится к интерфейсу классов, которые вы вызываете, или потому, что вы считаете, что ведение журнала слишком низкоуровневое для виртуальных функций? Если это проблема низкого уровня, это полная чушь. Виртуальные функции не могут сломаться, если код вашего приложения уже не имеет серьезных повреждений. Если дело в интерфейсе, то лучше использовать шаблон «Посетитель». Длинные цепочки else if, основанные на типе, никогда не бывают правильными.   -  person Omnifarious    schedule 19.08.2010


Ответы (5)


Во-первых, используйте конструкцию более высокого уровня для таких строк, как std::string.
Во-вторых, если вам нужно проверить тип окна, ваш дизайн неверен.
Используйте принцип подстановки Лисков для правильного проектирования.
В основном это означает, что любой из производных Window объектов может быть заменен его суперклассом.
Это может происходит только в том случае, если оба используют один и тот же интерфейс, а производные классы не нарушают контракт, предоставленный базовым классом.
Если вам нужен какой-то механизм для динамического применения поведения, используйте Шаблон посетителя

person the_drow    schedule 19.08.2010
comment
На самом деле, использование std::string здесь не важно, в конце концов, нет проблемы владения. - person Matthieu M.; 19.08.2010
comment
@Matthieu M.: Верно, но это был общий совет от опытного разработчика C++ новичку. Использование std::string очень важно, оно значительно упрощает работу со строками. - person the_drow; 19.08.2010
comment
@Blindy - Посетитель может быть правильным путем. Это либо посетитель, либо добавление виртуального метода в базовый класс. - person Omnifarious; 19.08.2010
comment
+1. Не могу поручиться за паттерн посетителя (паттерны ненависти в целом), но общий дух того, что дизайн неверен, сохраняется. В целом в C++ полиморфизм является официальным механизмом, позволяющим избежать переключения/регистра типов. Если кто-то пытается закодировать переключатель/регистр, то в дереве наследования чего-то не хватает. - person Dummy00001; 19.08.2010

Вот что нужно сделать в порядке предпочтения:

  1. Добавьте новый виртуальный метод в базовый класс и просто вызовите его. Затем поместите одноименный виртуальный метод в каждый производный класс, который реализует внутри него соответствующее предложение else if. Это предпочтительный вариант, поскольку ваша текущая стратегия является широко признанным признаком плохого дизайна, и это рекомендуемое средство.
  2. Используйте ::std::map< ::std::string, void (*)(Window *pWindow)>. Это позволит вам найти функцию для вызова на карте, которую гораздо быстрее и проще добавить. Это также потребует, чтобы вы разделили каждое предложение else if на отдельную функцию.
  3. Используйте ::std::map< ::std::string, int>. Это позволит вам найти целое число для соответствующей строки, а затем вы можете switch на целом числе.

Существуют и другие стратегии рефакторинга, которые больше напоминают вариант 1. Например, если вы не можете добавить метод в класс Window, вы можете создать интерфейсный класс с нужным методом. Затем вы можете создать функцию, которая использует dynamic_cast, чтобы выяснить, реализует ли объект класс интерфейса, и вызвать метод в этом случае, а затем обработать несколько оставшихся случаев с помощью вашей конструкции else if.

person Omnifarious    schedule 19.08.2010

Создайте словарь (set/hashmap) со строками в качестве ключей и поведением в качестве значения.

Использование поведения в качестве значений может осуществляться двумя способами:

  1. Инкапсулируйте каждое поведение в свой собственный класс, который наследуется от интерфейса с помощью метода «DoAction», который выполняет поведение.
  2. Используйте указатели функций

Обновление: я нашел эту статью, которая может быть тем, что вы ищете: http://www.dreamincode.net/forums/topic/38412-the-command-pattern-c/

person Dror Helper    schedule 19.08.2010

Вы можете попробовать поместить все ваши значения typeid(...).name() на карту, а затем выполнить find() на карте. Вы можете сопоставить с int, который можно использовать в операторе switch, или с указателем на функцию. А еще лучше, вы можете еще раз подумать о том, чтобы получить виртуальную функцию внутри каждого из типов, которая делает то, что вам нужно.

person Tony Delroy    schedule 19.08.2010
comment
Работаю в модуле регистратора. Я думал, что регистратор не должен вызывать виртуальную функцию производного класса для ведения журнала. Это должно сделать самостоятельно. Поэтому я отказался от подхода виртуальной функции. - person bjskishore123; 19.08.2010
comment
Эммм... почему ты так думаешь? Я не вижу причин избегать этого, и, как вы видите, это вызывает проблемы.... - person Tony Delroy; 19.08.2010

То, о чем вы просите, возможно, это также вряд ли будет хорошим решением вашей проблемы.

По сути, цепочка if/else if/else уродлива, поэтому первое решение, которое приходит на ум, будет использовать конструкцию, которая поднимет это, на ум приходит ассоциативный контейнер, и по умолчанию, очевидно, std::unordered_map.

Думая о типе этого контейнера, вы поймете, что вам нужно использовать имя типа в качестве ключа и связать его с объектом функтора...

Однако для этого есть гораздо более элегантные конструкции. Прежде всего, конечно, будет использование метода virtual.

class Base
{
public:
  void execute() const { this->executeImpl(); }
private:
  virtual void executeImpl() const { /* default impl */ }
};

class Derived: public Base
{
  virtual void executeImpl() const { /* another impl */ }
};

Это объектно-ориентированный способ справиться с этим типом требований.

Наконец, если вы обнаружите, что хотите добавить множество различных операций в свою иерархию, я предлагаю использовать хорошо известный шаблон проектирования: посетитель. Существует вариант под названием «Ациклический посетитель», который помогает справляться с зависимостями.

person Matthieu M.    schedule 19.08.2010
comment
Я не верю, что ::std::unordered_map является частью стандарта. Может быть, вы имели в виду ::std::tr1::unordered_map? - person Omnifarious; 19.08.2010
comment
@Omnifarious: это зависит от того, какой стандарт является частью C++0x окончательного проекта. По сути, вы всегда можете иметь, в зависимости от вашего STL, std::hash_map, std::unordered_map, std::tr1::unordered_map или boost::unordered_map (если у вас ничего нет в вашем STL). В наши дни я просто отвечаю, имея в виду C++0x, если не указана конкретная версия компилятора. - person Matthieu M.; 19.08.2010