Подтвердить что ты не робот

Метапрограммирование C/С++ с использованием препроцессора

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

Каждая запись выглядит так:

} else if ( strcmp(key, "key_string") == 0) {
    ((class_name*)object)->do_something();
} else if ( ...

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

Теперь, если бы я должен был кодировать это на языке более высокого уровня, я бы использовал словарь lambdas, чтобы упростить это.

Мне пришло в голову, что я могу использовать макросы, чтобы упростить это для чего-то вроде

case_call("key_string", class_name, do_something());
case_call( /* ... */ )

где case_call будет макросом, который расширяет этот код до первого фрагмента кода.

Однако, я очень нахожусь на заборе, будет ли это считаться хорошим стилем. Я имею в виду, что это уменьшит работу набрав и улучшит СУЩНОСТЬ кода, но тогда, похоже, это несколько портит макросистему.

Не могли бы вы пойти по этой дороге или, скорее, напечатать все это? И что бы вы сделали для этого?

Edit

Некоторые пояснения:

Этот код используется как слой клея между упрощенным скриптовым API, который обращается к нескольким различным аспектам С++ API как к простым свойствам ключа. Свойства реализованы по-разному в С++: у некоторых есть методы getter/setter, некоторые из них заданы в специальной структуре. Скриптовые действия ссылаются на объекты С++, переданные в общий базовый класс. Однако некоторые действия доступны только для определенных подклассов и должны быть отброшены.

Далее по дороге я могу изменить фактический С++ API, но на данный момент его нужно считать неизменным. Кроме того, это должно работать на встроенный компилятор, поэтому boost или С++ 11 (к сожалению) недоступны.

4b9b3361

Ответ 1

Я бы предложил вам немного изменить роли. Вы говорите, что объект уже является некоторым классом, который знает, как обрабатывать определенную ситуацию, поэтому добавьте virtual void handle(const char * key) в ваш базовый класс и пусть объект проверяет реализацию, если она применима к нему и делает все, что необходимо.

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

Ответ 2

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

Общая мудрость заключается в использовании клавиш сопоставления таблиц для действий:

std::map<std::string, void(Class::*)()> table;

Затем найдите и вызовите действие за один раз:

object->*table[key]();

Или используйте find для проверки отказа:

const auto i = table.find(key);
if (i != table.end())
    object->*(i->second)();
else
    throw std::runtime_error(...);

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

Спросите себя: почему мои функции принимают разные аргументы? Почему я использую броски? Если вы отправляете информацию о типе объекта, скорее всего, вам нужно ввести общий интерфейс.