[Из песочницы] Как enum доступным всем сделать, да в мета-тип записать
Преамбула
В процессе разработке ПО у меня возникла необходимость определения перечислителей enum в централизованном заголовочном файле, тогда как их использование могло быть во многих исходных файлах. Это весьма удобно с точки зрения организации исходников и зависимостей. Однако для моих задач также требовалась регистрация перечислителей в системе метатипов Qt. О том, как делал я такую регистрацию и пойдет речь.
Интервью с Qt
— Мне нужно чтобы ты понимал мой enum.
— Для объявления enum в качестве мета-типа для QVariant подойдет Q_DECLARE_METATYPE (). Макрос следует вызывать сразу после объявления enum.— Отлично! А если я хочу кидаться своим enum от сигналов к слотам, да в свойства пихать?
— Для регистрации enum в системе мета-типов потребуется qRegisterMetaType<T> (). Обратите внимание, это функция. Ее надо где-то вызвать. Желательно, до любого швыряния и пихания. В main (), например. И, да, без Q_DECLARE_METATYPE () ничего не получится.— Хм… Это не удобно. Хочу чтобы вся регистрация ограничивалась одним местом в исходниках…
— Что же вы сразу не сказали?! Используйте Q_ENUM ()! Этот замечательный макрос все сделает за вас! И даже больше! Он пропишет конверсию из вашего enum в QString для QVariant! Вам всего лишь надо вызвать макрос сразу после объявления enum внутри определения класса, наследованного от QObject…— Погоди, что? Нужно делать это в классе?… А я хотел глобально и для всех…
Возможности
- Если нам достаточно просто хранить экземпляры нашего enum в QVariant, то нам хватит макроса Q_DECLARE_METATYPE (). Передавать значение через сигналы-слоты, хранить в свойствах можно и в int, а по мере надобности приводить его явно к нашему enum.
- Если нам очень важно различать просто int от нашего enum, или хотим иметь пачку invokable методов с одним именем, но различием по типу аргумента, то уже не обойтись без дополнительной регистрации в системе мета-типов. Можно делать это вызовом qRegisterMetaType<T> () где-то до фактического использования.
- Ну, а если enum является частью некоторого QObject-класса, то все решается Q_ENUM (), после этого с ним можно делать что угодно. Даже использовать в кач-ве свойства или параметра слота в другом классе через полное имя типа (ClassName: EnumName).
Решение
Моя целевая задача, сформулированная ранее, требовала полноценной регистрации в системе мета-типов. Мне подходили две последних возможности. У возможности номер 2 недостаток в необходимости регистрировать enum явным вызовом функции где-то в коде. У возможности номер 3 недостаток в потенциальном усложнении зависимостей исходников, если размещать enum внутрь одного из имеющихся классов, работающих с ним.
Но никто же не мешает нам создать новый QObject-класс! В общем для всех заголовочном файле. И можно запретить инстанцирование этого класса. Приведу пример (а для разминки, в нем же небольшой пример возможностей стандарта c++11):
/// Набор глобальных перечислителей
class Enums : public QObject {
public:
/// достуно с std=с++11, 'class' прячет имена значений внутри имени типа EnumA, ':int' фиксирует размер на указанном int
enum class EnumA: int {
A,
B,
C,
};
Q_ENUM(EnumA)
enum EnumB {
A,///< коллизий с EnumA::A не будет, EnumA::A не доступно как A, в отличие от EnumB::A
D,
};
Q_ENUM(EnumB)
Q_OBJECT
Enums() = delete; ///< std=c++11, обеспечивает запрет на создание любого экземпляра Enums
};
В результате мы имеем класс Enums доступный всем желающим, имеющий нужные enum, известные системе мета-типов, и не имеющий возможность создавать экземпляры себя!
Однако он имеет некоторые минусы. Макрос Q_OBJECT создает, кроме статического мета-объекта, пачку ни разу не статических функций. А наш класс ни разу не будет создаваться! И эти функции никогда не понадобятся. Увы, это некоторая трата неизбежна. Однако, начиная с версии Qt 5.5, в документации описан макрос Q_GADGET.
— Да, да, он легче, чем Q_OBJECT, и не требует наследования от QObject. Разрешает только Q_ENUM, Q_PROPERTY, Q_INVOKABLE. И давно он уже существует, мы просто молчали о нем!
Действительно, что же вы молчали! Вот тут-то он нам отлично подойдет! Всего пара изменений, и штаны превращаются в…
/// Набор глобальных перечислителей
class Enums { /// < Обратите внимание, класс Enums теперь сам по себе!
public:
/// достуно с std=с++11, 'class' прячет имена значений внутри имени типа EnumA, ':int' фиксирует размер на указанном int
enum class EnumA: int {
A,
B,
C,
};
Q_ENUM(EnumA)
enum EnumB {
A,///< коллизий с EnumA::A не будет, EnumA::A не доступно как A, в отличие от EnumB::A
D,
};
Q_ENUM(EnumB)
Q_GADGET ///< Он легче Q_OBJECT и вообще скромняшка!
Enums() = delete; ///< std=c++11, обеспечивает запрет на создание любого экземпляра Enums
};
Нам, правда, не нужна возможность объявления свойств и invokable-методов. Но они неотъемлемая часть мета-объектов, отрезать не получится. Зато теперь, если сравнить старый и новый moc*.cpp на этот заголовочный файл, можно обнаружить пропажу реализации статической ф-ии qt_static_metacall () и обычных ф-ий metaObject (), qt_metacast (), qt_metacall (). Мелочь, а приятно. Любопытства ради, сравнил размеры бинарников, мелочь оказалась 512 байт (сборка Выпуск).
Заключение
Можно весьма легко определить enum в одном заголовочном файле, там же и закинув его в систему мета-типов, и далее использовать его в других классах. К примеру, у меня возникала задача использовать такой enum в некоторой основной логике системы, при этом предоставить возможность выбирать значения этого enum через интерфейс. Дополнительная фича Q_ENUM в виде регистрации имен значений enum позволила не изобретать велосипед, но это уже другая история. С моделями и шаблонами.
Комментарии (1)
5 ноября 2016 в 17:10
0↑
↓
Статья полезная, но форматирование с цитатами выглядит очень громоздко и затрудняет чтение. Нужно больше воздуха.