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

#c #rtti

#c #rtti

Вопрос:

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

 //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 становится слишком длинной.
Можем ли мы проверить тип окна с помощью switch или более простым методом ?

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

Комментарии:

1. Обычно, если вам приходится принимать решения на основе типа объекта, вы обнаружите, что рефакторинг вашего кода для улучшения дизайна решает проблему за вас.

2. Рассматривали ли вы специализацию шаблона??

3. @DumbCoder: Какое это имеет отношение к чему-либо? шаблоны предназначены для типа компиляции, и они также не принимают строки в качестве параметров.

4. @the_drow Когда я упоминал об использовании строк?

5. @DumbCoder: Затем использовать тип, который известен только во время выполнения? Что именно вы имели в виду.

Ответ №1:

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

Комментарии:

1. На самом деле, использование std::string здесь не важно, в конце концов, проблемы с владением нет.

2. @Matthieu M.: Верно, но это был общий совет от опытного разработчика C новичку. Важно использовать std::string, это значительно упрощает обработку строк.

3. @Blindy — Visitor может быть правильным решением. Это либо visitor, либо добавление виртуального метода в базовый класс.

4. 1. Не могу поручиться за шаблон visitor (ненавижу шаблоны в целом), но общий настрой на то, что дизайн неправильный, остается. В целом в C полиморфизм является официальным механизмом, позволяющим избежать переключения / регистра в типах. Если кто-то пытается закодировать switch / case, то в дереве наследования чего-то не хватает.

Ответ №2:

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

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

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

Ответ №3:

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

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

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

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

Ответ №4:

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

Комментарии:

1. Я работаю в модуле logger. Я думал, logger не должен вызывать виртуальную функцию производного класса для целей ведения журнала. Это должно сработать само по себе. Поэтому я отказался от подхода с использованием виртуальных функций.

2. Ммм… почему вы так думаете? Я не вижу никаких причин избегать этого, и, как вы можете видеть, это вызывает проблемы….

Ответ №5:

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

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

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

Однако для этого существуют гораздо более элегантные конструкции. Первым из всех будет, конечно, использование 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 */ }
};
  

Это OO-способ справиться с требованиями такого типа.

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

Комментарии:

1. Я не верю, что это ::std::unordered_map является частью стандарта. Может быть, вы имеете в виду ::std::tr1::unordered_map ?

2. @Omnifarious: это зависит от того, какой стандарт, это часть C 0x окончательного проекта. Фактически вы всегда можете иметь, в зависимости от вашего STL, std::hash_map , std::unordered_map std::tr1::unordered_map или boost::unordered_map (если у вас ничего нет в вашем STL). В наши дни я просто отвечаю, имея в виду C 0x, если только не указана конкретная версия компилятора.