Альтернатива Macro для генерации кода на C ++

В моем модуле настроек есть код:

#include  class MySettings { public: // param1 void setParam1(QString param1) { _settings.setValue("param1", param1); } string param1() { return _settings.value("param1").toString(); } // param2 void setParam2(int param2) { _settings.setValue("param2", param2); } int param2() { _settings.value("param2").toInt(); } // param3 void setParam3(int param3) { _settings.setValue("param3", param3); } int param3() { _settings.value("param3").toInt(); } private: QSettings _settings; } 

Мне удалось уменьшить код amout для записи с помощью макроса. Здесь приведен пример типа параметра QString :

 #define INTSETTING(setter, getter) \ void set##setter(QString getter) { settings.setValue(#getter, getter);} \ QString getter() {return settings.value(#getter).toString();} 

Поскольку я использую C ++, я знаю, что использование макросов плохо, поэтому я ищу более чистую альтернативу.

Я дал пример Qt (QString), но это более общий вопрос.

Редактировать:

Что делает определение вышеописанного classа намного проще:

 class MySettings { public: STRINGSETTING(Param1, param1) INTSETTING(Param2, param2) INTSETTING(Param3, param3) STRINGSETTING(DefaultTitle, defaultTitle) INTSETTING(MaxDocCount, maxDocCount) private: QSettings _settings; } 

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

Есть много людей, которые отвечают на это религиозным образом, они просто ненавидят препроцессор и все, что с ним связано, и запрещают его использование из своего кода.

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

Я думаю, то, как люди думают об этом, очень похоже на то, как люди думают о утверждении goto : большинство деамонизируют его использование, другие говорят, что оно имеет свои положительные цели и не должно рассматриваться как зло само по себе. Вам нужно решить это для себя.

Вот один из способов, который не использует macros:

 class MySettings { public: template  void setParam(QString param) { _settings.setValue(names[N], param); } template  T param() { return _settings.value(names[N]).toString(); } private: QSettings _settings; const char* names[3] = { "param1", "param2", "param3" }; } 

Вы немного меняете синтаксис, так что скажите, например, settings.setParam<1>("string") и settings.param<1, string>() но в любом случае имена param1, param2 т. Д. Не были настолько информативными.

Единственное неудобство заключается в том, что вызывающему абоненту необходимо указать возвращаемый тип param() кроме номера параметра. Чтобы избавиться от этого, вы можете указать все типы параметров в MySettings , например:

 class MySettings { using types = std::tuple; public: template void setParam(QString param) { _settings.setValue(names[N], param); } template typename std::tuple_element::type param() { return _settings.value(names[N]).toString(); } private: QSettings _settings; const char* names[3] = { "param1", "param2", "param3" }; } 

Конечно, вы могли бы дополнительно обобщить этот class, который будет использоваться в качестве базы для других classов настроек. Внутри базы единственные вещи, которые нужно настроить, – это types и names членов.

Однако имейте в виду, что если имена параметров информативны, в отличие от вашего примера, например setTitle , setColor и т. setTitle , setTitle , скорее всего, нет способа избежать макросов. В этом случае я предпочитаю макрос, который генерирует целую структуру, а не fragment кода в другом classе, следовательно, возможно, загрязняя ее область действия. Таким образом, может существовать структура для каждого отдельного параметра, сгенерированного макросом, с учетом имени параметра. Тогда class настроек наследует все эти отдельные структуры.

РЕДАКТИРОВАТЬ

Я «забыл» обобщение toString() в param() (спасибо @Joker_vD). Один из способов:

  template typename std::tuple_element::type param() { using T = typename std::tuple_element::type; return get_value(type(), _settings.value(names[N])); } 

где get_value() – вспомогательная функция, которую вам нужно определить и перегрузить для типов, поддерживаемых QSettings , вызывая соответствующую функцию члена преобразования для каждого типа, например

  template string get_value(type, const V& val) { return val.toString(); } template int get_value(type, const V& val) { return val.toInt(); } 

и type – это только вспомогательная структура:

  template struct type { }; 

Если сам QSettings был разработан с учетом шаблонов, вам это не понадобится. Но вам, вероятно, не понадобится обертка в первую очередь.

Скрытие членов – это хорошо. Но когда вы позволяете пользователю редактировать / видеть их, вы должны наложить некоторые ограничения: в каждом setter должна быть проверка перед назначением элементу нового значения (что может даже привести к сбою приложения). В противном случае существует небольшая разница, если данные были public .