Qt元系统之类型注册
Meta Type System
如果库或程序有一个在不知道类型的情况下还能拷贝和销毁对象的需求,怎么实现呢?
如果类型已知,很容易就能拷贝和销毁:
class type
{
};
int main()
{
type t;
type t1 = t;
type *pt = new type;
delete pt;
}
如果类型不可知呢?
在实际应用中,类型在很多情况下都是不可知的,例如,qt库的开发人员就不知道qt库的使用者自定义的类型,而使用者往往希望qt库能对这些类型的对象进行存储、销毁等操作,否则,异步的信号和槽就无法使用用户自定义的类型,因为,qt需要存储调用参数;QVariant也无法使用用户自定义的类型,因为他需要知道怎么存储和销毁该类型的对象。
为了解决这个问题,Qt实现了一个Meta Type System
。
想想为什么不用模板呢?
QMetaType是元类型系统提供给外部的接口,上面的类图省略了很多东西,着重给出了构造器、析构器、type id、类型大小这几个成员变量以及从type id构造QMetaType的构造函数、注册类型的接口、从地址指向的数据构造一份数据的接口、对指定地址的数据进行析构并归还内存空间的接口。
qt不可能采取浸入式的设计,因为在设计库的时候不可能知道每一个用户将会定义什么类型,它只可能知道自己定义使用的类型,因此,qt必须采用非浸入式的设计。
元类型系统的背后,是一系列数据结构的组合,例如,系统需要设计数据结构表示类型信息,存储类型信息,还需要接口查询类型信息,注册类型信息。
元类型系统中,每一个注册进去的类型都拥有一个type id
,它就是这个类型的身份证编号。
有了这个系统,就能让qt库在不知道我们的类型的情况下拷贝和销毁该类型的对象:
register_type_1.h
#include <QString>
class MyClass
{
public:
MyClass() = default;
MyClass(const MyClass &_t){
m_str = _t.m_str;
}
QString m_str;
};
Q_DECLARE_METATYPE(MyClass) //延迟注册,非浸入式的设计
#include "register_type_1.h"
int main(int argc, char *argv[])
{
//### test 1 ---qt static type
QString str = "test qt static type";
//获取QString的type id
int str_type_id = QMetaTypeId2<QString>::qt_metatype_id();
//从type id构造出元类型对象
QMetaType Str_static_meta_type(str_type_id);
/*
构造出了元对象之后,我们就能用这个对象做一些事情啦
*/
//获取QString类型的大小
int str_type_size = Str_static_meta_type.sizeOf();
//从&str生成str的拷贝,并返回指向它的地址
QString *str_cp = static_cast<QString*>(Str_static_meta_type.create(&str));
//销毁拷贝,析构
Str_static_meta_type.destroy(str_cp);
QString test = *str_cp; //oops...访问一个已经析构的内存
//### test 2 ---qt dynamic[customer] type
MyClass mc;
mc.m_str = "test customer type";
int mc_type_id = QMetaTypeId2<MyClass>::qt_metatype_id();
QMetaType Mc_customer_meta_type(mc_type_id);
int mc_type_size = Mc_customer_meta_type.sizeOf();
MyClass *mc_cp = static_cast<MyClass*>(Mc_customer_meta_type.create(&mc));
Mc_customer_meta_type.destroy(mc_cp);
mc_cp = nullptr;
}
在test2中:
QMetaType Mc_customer_meta_type(mc_type_id);
Mc_customer_meta_type.create(&mc)
Mc_customer_meta_type.destroy(mc_cp);
这三条语句表现出了元类型系统的核心能力:
只需要一个类型的type id,就可以拥有构造和析构该类型的对象的能力!
这个能力让qt库不必知道类型就能存储和销毁它的对象,只要使用者记得向系统注册这个类型:
template <typename T>
int qRegisterMetaType(const char *typeName)
//qRegisterMetaType<MyClass>("MyClass");
场景
很多时候,我们需要将自己定义的类型融入到Qt的Meta system中,例如,用QVariant存储自定义的类对象。先看看如果什么都不做,会出现什么问题:
/*
* 示例代码一
*/
#include <QString>
class MyClass
{
public:
MyClass() = default;
MyClass(const MyClass &_t){
m_str = _t.m_str;
}
QString m_str;
};
int main(int argc, char *argv[])
{
MyClass mc;
/*
QVariant对类MyClass使用QMetaTypeId2<MyClass>,如果我们不提供QMetaTypeId2<MyClass>的
特化版,那么编译器就会用qt提供的模板自动生成这个类。
*/
QVariant v = QVariant::fromValue(mc);
/*
从v中返回类型为MyClass的数据
*/
mc = v.value<MyClass>();
return 0;
}
启动编译器进行编译,结果如下:
error C2338: Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt’s meta-object system
出现编译错误,怎么回事?
qt提供了如下templates:
//偏特化
template <typename T>
struct QMetaTypeIdQObject<T*, QMetaType::PointerToQObject>
{
enum {
Defined = 1