Как enum доступным всем сделать, да в мета-тип записать

в 12:23, , рубрики: c++, enum, qmetatype, qobject, qt, Проектирование и рефакторинг

Преамбула

В процессе разработке ПО у меня возникла необходимость определения перечислителей enum в централизованном заголовочном файле, тогда как их использование могло быть во многих исходных файлах. Это весьма удобно с точки зрения организации исходников и зависимостей. Однако для моих задач также требовалась регистрация перечислителей в системе метатипов Qt. О том, как делал я такую регистрацию и пойдет речь.

Интервью с Qt

— Мне нужно чтобы ты понимал мой enum.

— Для объявления enum в качестве мета-типа для QVariant подойдет Q_DECLARE_METATYPE(). Макрос следует вызывать сразу после объявления enum.

— Отлично! А если я хочу кидаться своим enum от сигналов к слотам, да в свойства пихать?

— Для регистрации enum в системе мета-типов потребуется qRegisterMetaType&ltT&gt(). Обратите внимание, это функция. Ее надо где-то вызвать. Желательно, до любого швыряния и пихания. В main(), например. И, да, без Q_DECLARE_METATYPE() ничего не получится.

— Хм… Это не удобно. Хочу чтобы вся регистрация ограничивалась одним местом в исходниках…

— Что же вы сразу не сказали?! Используйте Q_ENUM()! Этот замечательный макрос все сделает за вас! И даже больше! Он пропишет конверсию из вашего enum в QString для QVariant! Вам всего лишь надо вызвать макрос сразу после объявления enum внутри определения класса, наследованного от QObject...

— Погоди, что? Нужно делать это в классе?.. А я хотел глобально и для всех…

Возможности

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

Автор: SeroBuro

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js