1

QMetaType类

QMetaType类管理Qt的元类型系统,它将类型名称与类型关联,从而可以在run-time时创建、销毁。Q_DECLARE_METATYPE() 宏静态,使类型可以用于基于模板的方法,qRegisterMetaType()方法动态 让类型可用于非模板方法。
QT内置类型都已经注册enum QMetaType::Type,enum QMetaType::Type从0开始,其中 0 为UnknowType, 1024为用户自定义类型起始位置。

Q_DECLARE_METATYPE()宏与int qMetaTypeId()

此二者用于编译时,自定义类型只要满足以下条件就可以用宏 Q_DECLARE_METATYPE()类注册为元类型。

  • public default constructor

  • public copy constructor

  • public destructor

使用Q_DECLARE_METATYPE(T)时类型T(包括指针类型)必须是完全定义的,正常情况下就在类型定义之后注册,或者在每次 都要包含的私有头文件中。
对于前置声明类型的指针可以使用宏Q_DECLARE_OPAQUE_POINTER(),之后可以正常注册。
如果T在namespace中, Q_DECLARE_METATYPE(T)必须在namespace之外,T要包含完全限定名:


namespace MyNamespace
{
    ...
}
Q_DECLARE_METATYPE(MyNamespace::MyStruct)

Q_DECLARE_METATYPE在compile time注册类型,使类型T可以用于所有的模板类,包括QVariant。
如果要在QueuedConnections的信号槽连接中使用类型T,还需要使用qRegisterMetaType()来注册,因为QueuedConnections信号槽参数是动态查找的。
qMetaTypeId<T>()可以在编译时获取注册的类型id,如果T没有使用Q_DECLARE_METATYPE注册,会产生编译错误。

int id = qMetaTypeId<QString>();    // id is now QMetaType::QString
id = qMetaTypeId<MyStruct>();       // compile error if MyStruct not declared

具体实现

#define Q_DECLARE_METATYPE(TYPE) Q_DECLARE_METATYPE_IMPL(TYPE)#define Q_DECLARE_METATYPE_IMPL(TYPE)                                   \
    QT_BEGIN_NAMESPACE                                                  \
    template <>                                                         \
    struct QMetaTypeId< TYPE >                                          \
    {                                                                   \
        enum { Defined = 1 };                                           \
        static int qt_metatype_id()                                     \
            {                                                           \
                static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \
                if (const int id = metatype_id.loadAcquire())           \
                    return id;                                          \
                const int newId = qRegisterMetaType< TYPE >(#TYPE,      \
                              reinterpret_cast< TYPE *>(quintptr(-1))); \
                metatype_id.storeRelease(newId);                        \
                return newId;                                           \
            }                                                           \
    };                                                                  \
    QT_END_NAMESPACE

Q_DECLARE_METATYPE宏首先定义QMetaTypeId的具现,这时还没有注册ID,只有在调用静态方法qt_metatype_id之后才会间接调用qRegisterMetaType注册类型id。
再来看qMetaTypeid()方法,类型必须已经有QMetaTypeId的具现,最终调用qRegisterMetaType注册类型,如果只有宏,类型实际上并没有注册,QMeta::type()也就查不到id,Queued信号槽也不能传递该参数。

template <typename T>
inline Q_DECL_CONSTEXPR int qMetaTypeId()
{
    Q_STATIC_ASSERT_X(QMetaTypeId2<T>::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system");
    return QMetaTypeId2<T>::qt_metatype_id();
}
template <typename T>
struct QMetaTypeId2
{
    enum { Defined = QMetaTypeId<T>::Defined, IsBuiltIn=false };
    static inline Q_DECL_CONSTEXPR int qt_metatype_id() { return QMetaTypeId<T>::qt_metatype_id(); }
};

qRegisterMetaType()方法与QMetaType::type()

qRegisterMetaType()就是将类型与类型名关联,之后可以用QMetaType::type()查找类型id

typedef QString CustomString;
qRegisterMetaType<CustomString>("CustomString");

QMetaType::type()类似qMetaTypeId返回类型id,不过是在runtime查找注册的类型,而qMetaTypeId在编译时。
const char * QMetaType::typeName(int typeId)
同一类型的不同alias注册后的id为同一值,查找时获取的是最原始的类型名。

关于QT内置类型以及类型alias

  • QT内置类型已经有id,可以用Q_DECLARE_METATYPE宏定义,不会报错,但也没什么效果,因为不使用宏调用qMetaTypeid()可以获取类型id,不调用qMetaTypeid()就可以用QMeta::type()查到id,说明已经注册。

  • QT内置类型的alias,可以用Q_DECLARE_METATYPE宏定义,不会报错,但也没什么效果,调用qMetaTypeid()可以获取原类型id, 但是QMeta::type()不行(调用qMetaTypeid()之后也不行), 所以内置类型别名用于Queued信号槽还需要调用qRegisterMetaType注册类型名。

  • 自定义类型在Q_DECLARE_METATYPE宏定义,调用qMetaTypeid()后,QMeta::type()可以查找到正确id。

  • 自定义类型alias与原类型间只能有一个用Q_DECLARE_METATYPE宏定义,其余的具现化都会编译错误,但只要一个用宏定义后看,其余的都能用qMetaTypeid()查询到正确id,QMeta::type()需要在qMetaTypeid()或qRegisterMetaType之后才能正确查找。


silvercore
15 声望1 粉丝