Qt: reflection of enumeration type

Posted by cdrees on Fri, 28 Jan 2022 14:01:10 +0100

catalogue

1, Macro expansion

2, Q_ What are the benefits of enum?

3, Reference and use in specific projects

    enum DockOption {
        AnimatedDocks = 0x01,
        AllowNestedDocks = 0x02,
        AllowTabbedDocks = 0x04,
        ForceTabbedDocks = 0x08,  // implies AllowTabbedDocks, !AllowNestedDocks
        VerticalTabs = 0x10,      // implies AllowTabbedDocks
        GroupedDragging = 0x20    // implies AllowTabbedDocks
    };
    Q_ENUM(DockOption)
    Q_DECLARE_FLAGS(DockOptions, DockOption)
    Q_FLAG(DockOptions)

First look at the code above; When looking at the source code of Qt, we see that a large number of enumerations adopt the above form, so let's focus on the analysis today.

Start our soul three questions, Q_ENUM ,Q_DECLARE_FLAGS and Q_ What is flag? What are the benefits of this use? How can we learn from Qt programming?

1, Macro expansion

#define Q_ENUM(x) Q_ENUMS(x) Q_ENUM_IMPL(x)

#define Q_ENUMS(x) QT_ANNOTATE_CLASS(qt_enums, x)


# ifndef Q_COMPILER_VARIADIC_MACROS
#  define QT_ANNOTATE_CLASS(type, x)
# else
#  define QT_ANNOTATE_CLASS(type, ...)
# endif

#define Q_ENUM_IMPL(ENUM) \
    friend Q_DECL_CONSTEXPR const QMetaObject *qt_getEnumMetaObject(ENUM) Q_DECL_NOEXCEPT { return &staticMetaObject; } \
    friend Q_DECL_CONSTEXPR const char *qt_getEnumName(ENUM) Q_DECL_NOEXCEPT { return #ENUM; }


// ---Further expansion and replacement

#define Q_ENUM(x) Q_ENUMS(x) Q_ENUM_IMPL(x) is equivalent to = =

    QT_ANNOTATE_CLASS(type,ENUM) \
    friend Q_DECL_CONSTEXPR const QMetaObject *qt_getEnumMetaObject(ENUM) Q_DECL_NOEXCEPT { return &staticMetaObject; } \
    friend Q_DECL_CONSTEXPR const char *qt_getEnumName(ENUM) Q_DECL_NOEXCEPT { return #ENUM; }

#define Q_DECLARE_FLAGS(Flags, Enum)\
typedef QFlags<Enum> Flags;


template<typename Enum>
class QFlags
{
    // If the conditions are not met, an error prompt will be given after the compilation process = =, which is worth learning from
    Q_STATIC_ASSERT_X((sizeof(Enum) <= sizeof(int)),
                      "QFlags uses an int as storage, so an enum with underlying "
                      "long long will overflow.");
    Q_STATIC_ASSERT_X((std::is_enum<Enum>::value), "QFlags is only usable on enumeration types.");

    struct Private;
    typedef int (Private::*Zero);
    template <typename E> friend QDataStream &operator>>(QDataStream &, QFlags<E> &);
    template <typename E> friend QDataStream &operator<<(QDataStream &, QFlags<E>);
public:
#if defined(Q_CC_MSVC) || defined(Q_CLANG_QDOC)
    // see above for MSVC
    // the definition below is too complex for qdoc
    typedef int Int;
#else
    typedef typename std::conditional<
            std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
            unsigned int,
            signed int
        >::type Int;
#endif
    typedef Enum enum_type;
    // compiler-generated copy/move ctor/assignment operators are fine!
#ifdef Q_CLANG_QDOC
    Q_DECL_CONSTEXPR inline QFlags(const QFlags &other);
    Q_DECL_CONSTEXPR inline QFlags &operator=(const QFlags &other);
#endif
    Q_DECL_CONSTEXPR inline QFlags(Enum flags) Q_DECL_NOTHROW : i(Int(flags)) {}
    Q_DECL_CONSTEXPR inline QFlags(Zero = Q_NULLPTR) Q_DECL_NOTHROW : i(0) {}
    Q_DECL_CONSTEXPR inline QFlags(QFlag flag) Q_DECL_NOTHROW : i(flag) {}

#ifdef Q_COMPILER_INITIALIZER_LISTS
    Q_DECL_CONSTEXPR inline QFlags(std::initializer_list<Enum> flags) Q_DECL_NOTHROW
        : i(initializer_list_helper(flags.begin(), flags.end())) {}
#endif

    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator&=(int mask) Q_DECL_NOTHROW { i &= mask; return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator&=(uint mask) Q_DECL_NOTHROW { i &= mask; return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator&=(Enum mask) Q_DECL_NOTHROW { i &= Int(mask); return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator|=(QFlags other) Q_DECL_NOTHROW { i |= other.i; return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator|=(Enum other) Q_DECL_NOTHROW { i |= Int(other); return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator^=(QFlags other) Q_DECL_NOTHROW { i ^= other.i; return *this; }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &operator^=(Enum other) Q_DECL_NOTHROW { i ^= Int(other); return *this; }

    Q_DECL_CONSTEXPR inline operator Int() const Q_DECL_NOTHROW { return i; }

    Q_DECL_CONSTEXPR inline QFlags operator|(QFlags other) const Q_DECL_NOTHROW { return QFlags(QFlag(i | other.i)); }
    Q_DECL_CONSTEXPR inline QFlags operator|(Enum other) const Q_DECL_NOTHROW { return QFlags(QFlag(i | Int(other))); }
    Q_DECL_CONSTEXPR inline QFlags operator^(QFlags other) const Q_DECL_NOTHROW { return QFlags(QFlag(i ^ other.i)); }
    Q_DECL_CONSTEXPR inline QFlags operator^(Enum other) const Q_DECL_NOTHROW { return QFlags(QFlag(i ^ Int(other))); }
    Q_DECL_CONSTEXPR inline QFlags operator&(int mask) const Q_DECL_NOTHROW { return QFlags(QFlag(i & mask)); }
    Q_DECL_CONSTEXPR inline QFlags operator&(uint mask) const Q_DECL_NOTHROW { return QFlags(QFlag(i & mask)); }
    Q_DECL_CONSTEXPR inline QFlags operator&(Enum other) const Q_DECL_NOTHROW { return QFlags(QFlag(i & Int(other))); }
    Q_DECL_CONSTEXPR inline QFlags operator~() const Q_DECL_NOTHROW { return QFlags(QFlag(~i)); }

    Q_DECL_CONSTEXPR inline bool operator!() const Q_DECL_NOTHROW { return !i; }

    Q_DECL_CONSTEXPR inline bool testFlag(Enum flag) const Q_DECL_NOTHROW { return (i & Int(flag)) == Int(flag) && (Int(flag) != 0 || i == Int(flag) ); }
    Q_DECL_RELAXED_CONSTEXPR inline QFlags &setFlag(Enum flag, bool on = true) Q_DECL_NOTHROW
    {
        return on ? (*this |= flag) : (*this &= ~Int(flag));
    }

private:
#ifdef Q_COMPILER_INITIALIZER_LISTS
    Q_DECL_CONSTEXPR static inline Int initializer_list_helper(typename std::initializer_list<Enum>::const_iterator it,
                                                               typename std::initializer_list<Enum>::const_iterator end)
    Q_DECL_NOTHROW
    {
        return (it == end ? Int(0) : (Int(*it) | initializer_list_helper(it + 1, end)));
    }
#endif

    Int i;
};
#define Q_FLAG(x) Q_FLAGS(x) Q_ENUM_IMPL(x)

#define Q_FLAGS(x) QT_ANNOTATE_CLASS(qt_enums, x)

// After comparing the expansion of the macro, we find that Q_ENUM and Q_FLAG is very similar

Therefore, in fact, here, an ordinary enumeration type is instantiated and converted through the qflags < enum > flags template type to realize more operations on the enumeration type value

2, Q_ What are the benefits of enum?

In some scenarios, we often worry about obtaining the corresponding enumeration type string from the enumeration value and obtaining the enumeration value through the enumeration type string. Qt uses Q_ENUM implements the reflection mechanism of enumeration types, which can be used flexibly to help us solve this big trouble. We mainly rely on the type QMetaEnum

class Q_CORE_EXPORT QMetaEnum
{
public:
    Q_DECL_CONSTEXPR inline QMetaEnum() : mobj(nullptr), handle(0) {}

    const char *name() const;
    const char *enumName() const;
    bool isFlag() const;
    bool isScoped() const;

    int keyCount() const;
    const char *key(int index) const;
    int value(int index) const;

    const char *scope() const;

    int keyToValue(const char *key, bool *ok = nullptr) const;
    const char* valueToKey(int value) const;
    int keysToValue(const char * keys, bool *ok = nullptr) const;
    QByteArray valueToKeys(int value) const;

    inline const QMetaObject *enclosingMetaObject() const { return mobj; }

    inline bool isValid() const { return name() != nullptr; }

    template<typename T> static QMetaEnum fromType() {
        Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value,
                          "QMetaEnum::fromType only works with enums declared as Q_ENUM or Q_FLAG");
        const QMetaObject *metaObject = qt_getEnumMetaObject(T());
        const char *name = qt_getEnumName(T());
        return metaObject->enumerator(metaObject->indexOfEnumerator(name));
    }

private:
    const QMetaObject *mobj;
    uint handle;
    friend struct QMetaObject;
};
Q_DECLARE_TYPEINFO(QMetaEnum, Q_MOVABLE_TYPE);

3, Reference and use in specific projects

As we said above, Q_ The biggest advantage of enum implementation is the reflection of types. Enumeration types are commonly used to represent a collection of data, actions, attributes, etc. in the process of programming. The specific use is often as follows:

enum AA{
    A1,
    A2,
    A3
    // ...
};

void do_something(AA type){

    switch(type):
    case A1:  do_A1();break;
    case A2:  do_A2();break;
    // ...
    default: break;
}

But in fact, in some specific scenarios, such as processing communication instructions, especially those in character format, we need to make another layer of conversion, as follows:

enum AA{
    A1,
    A2,
    A3
    // ...
};

void do_something(AA type){

    switch(type):
    case A1:  do_A1();break;
    case A2:  do_A2();break;
    // ...
    default: break;
}


AA stringToEnum(QString cmd){
    if( 0 == QString::copmare(cmd,"A1")) return A1;
    else if( 0 == QString::copmare(cmd,"A2")) return A2;
    // ...
}

int main(){

    while(1){
        string cmd = read_from_socket();
        AA type = stringToEnum(cmd);
        do_something(type);
    }

}

First, the instruction message of string type is converted into the operation type of enum type, and then called for message distribution to deal with the transaction. It seems that there is no problem, but with the increase of message instruction types, it is necessary to modify the enum definition and compare the corresponding strings in the stringToEnum() method every time, which is cumbersome.

Now, we use the enum reflection mechanism implemented by Qt, which is much more convenient. Specific examples are as follows. Let's take the father class in the previous article as an example:

// myfather.h

#ifndef MYFATHER_H
#define MYFATHER_H

#include <QObject>
#include <QString>
#include <QDebug>
#include <QMetaEnum>

class MyFatherPrivate;
class MyFather : public QObject
{
    Q_OBJECT
public:

    enum Father_action{
        DRINKING = 0x00,
        SMOKING = 0x01,
        PLAYING = 0x02,
        COOKING = 0x03
    };

    Q_ENUM(Father_action)
    Q_DECLARE_FLAGS(Father_actions,Father_action)
    Q_FLAG(Father_actions)


    explicit MyFather(QObject *parent = nullptr);

    void father_favourite(Father_actions type){
        if(type == DRINKING){
            do_drink();
        }else if(type == SMOKING){
            do_smoke();
        }else{
            qt_noop();
        }
        QMetaEnum m = QMetaEnum::fromType<Father_actions>();
        qDebug()<< "father is " << m.valueToKey(type);
    }

    void do_smoke();
    bool canSmoke(){ return smoke_enable;}
    void setSmokeEnable(bool enable){ smoke_enable = enable;}

    void do_drink();
    bool canDrink(){ return drink_enable;}
    void setDrinkEnable(bool enable){ drink_enable = enable;}
private:
    bool smoke_enable : 1;
    bool drink_enable : 1;
};

#endif // MYFATHER_H
// myfather.cpp

#include "myfather.h"

MyFather::MyFather(QObject *parent): QObject(parent)
{

}


void MyFather::do_smoke(){
    if(false == smoke_enable) return;
    qDebug() << "father can smoke.";
}

void MyFather::do_drink(){
    if(false == drink_enable) return;
    qDebug() << "father can drink.";

}
// Test: main cpp


#include <QCoreApplication>
#include <myfather.h>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyFather father;
    
    // Father's day? Father is allowed to smoke and drink a little wine today
    father.setSmokeEnable(true);
    father.setDrinkEnable(true);
    
    father.father_favourite(MyFather::DRINKING);

    // Here is our focus. The essence of the enum reflex mechanism is.
    // Even if the message types continue to increase in the future, we only need to be in father_ Add actions to the enumeration type
    // At the same time, add the corresponding message processing function. The inefficient string comparison can be skipped and the maintenance is simpler

    QMetaEnum m = QMetaEnum::fromType<MyFather::Father_actions>();
    int key =  m.keyToValue("SMOKING"); // 0x01
    father.father_favourite(MyFather::Father_actions(key));

    return a.exec();
}

Output result:

father can drink.
father is  DRINKING
father can smoke.
father is  SMOKING

 

Topics: C++ Qt