Як 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 дозволила не винаходити велосипед, але це вже інша історія. З моделями та шаблонами.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.