[Из песочницы] Как 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
 — Погоди, что? Нужно делать это в классе?… А я хотел глобально и для всех…

Возможности


  1. Если нам достаточно просто хранить экземпляры нашего enum в QVariant, то нам хватит макроса Q_DECLARE_METATYPE (). Передавать значение через сигналы-слоты, хранить в свойствах можно и в int, а по мере надобности приводить его явно к нашему enum.
  2. Если нам очень важно различать просто int от нашего enum, или хотим иметь пачку invokable методов с одним именем, но различием по типу аргумента, то уже не обойтись без дополнительной регистрации в системе мета-типов. Можно делать это вызовом qRegisterMetaType<T> () где-то до фактического использования.
  3. Ну, а если 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

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

© Habrahabr.ru