目录
1. 前言
2. Q_DECLARE_METATYPE
3. Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE
4. Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE
5. Q_DECLARE_SMART_POINTER_METATYPE
Qt通过Q_DECLARE开头的几个宏及几个qRegister开头的函数向Qt元系统注册一些非基本类型。一旦注册后,在Qt元系统中就可以很方便的利用这些非基本类型,这样对编程中的数据交互很方便。非基本类型是指除了一些基本类型之外的类型,如:除了int、float、qint......之外的类型,如:自定义类、结构体、枚举等。qRegister开头的函数用法参见如下:
Qt中以qRegister开头的几个函数的用法说明。
这个宏定义如下:
Q_DECLARE_METATYPE(Type)
这个宏能使Qt元系统的QMetaType知道Type类型,但前提是Type类型必须提供一个公有的默认构造函数、公有的默认拷贝构造函数和公有的默认析构函数。当在QVariant中使用自定义类型时,利用该宏向Qt元系统声明自定义类型是必须的,否则编译会报错。
如下为自定义的类:
#pragma once
#include
class CTest /* : public QObject */
{
public:
CTest() {}
CTest(const CTest&) {}
~CTest() {}
};
在main中,设置CTest对象到QVariant中:
#include
#include
#include
#include "test.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
CTest s;
QVariant varTest;
varTest.setValue(s); // 将s设置到 QVariant类对象中,以便后期取出来使用
return a.exec();
}
则报错如下:
C2338 static_assert failed: 'Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system'
C2039 "qt_metatype_id": 不是 "QMetaTypeId" 的成员
调用该宏时,Type必须是完全定义的类型,也就是说,调用该宏的时候Type类型对象是已经完全构造出来的。当一个类对象还在构造函数里面,即构造函数还没调用完成,则称为不是完全定义类型,就像动物或人怀孕在妈妈肚子里面还没生出来时,这个时候严格来说还不能叫真正的动物或人,因为还没成型。
通常理想的做法是:把这个宏放在类或结构体声明的下(后)方,如果做到这样很难,也可以放在一个专门的头文件中,当每次在QVariant中用Type类型时,包含该头文件即可。
添加一个Q_DECLARE_METATYPE()宏,能使type类型让所有基于模板的函数识别,包括QVariant。注意:如果是在基于队列的信号、槽连接或基于QObject的属性系统中,依然需要用qRegisterMetaType()进行注册该类型,因为这些名称是在运行时被决定的。
下面的例子,显示了Q_DECLARE_METATYPE()的典型用法:
struct MyStruct
{
int i;
...
};
Q_DECLARE_METATYPE(MyStruct) // 该宏放在类或结构体声明的最后面
如果MyStruct在命名空间中,Q_DECLARE_METATYPE()必须在命名空间外部,如下:
namespace MyNamespace
{
...
}
Q_DECLARE_METATYPE(MyNamespace::MyStruct) // 在命名空间的作用域外部,且放在命名空间声明的最下面
现在MyStruct能被QMetaType识别了,它也能被用于QVariant中:
MyStruct s;
QVariant var;
var.setValue(s); // 拷贝 s 到QVariant
...
// 取出QVariant中的值
MyStruct s2 = var.value();
一些类型被自动注册,所以这些类型不需要这个宏对其声明:
对于第1条说明如下:
将上面的CTest改为从QObject派生,且不用Q_DECLARE_METATYPE(CTest)进行元类型声明时,设置一个CTest的指针到QVariant则不会报错,如下:
CTest* s; // 指向CTest的指针
QVariant var;
var.setValue(s); // 拷贝 s 到QVariant,不会报错
但如果不用Q_DECLARE_METATYPE(CTest)进行元类型声明,设置一个CTest的非指针对象且到QVariant则会报错,如下会报错:
CTest s; // CTest是从QObject派生的子类
QVariant var;
var.setValue(s); // 拷贝 s 到QVariant,如果不用Q_DECLARE_METATYPE(CTest)进行元类型声明,会报错
这个宏对序列容器进行声明,以便Qt的元类型能知道该序列容器,这样就可以将一个序列容器的实例放到QVariant中,前提是Qt的元类型对序列容器中的每个元素是知道的,即序列容器中的每个元素是Qt的元类型或声明过或注册过的类型。
Qt的所有序列容器(如:QVector、QList等)都内置对该宏的支持,所以对于Qt的所有序列容器不需要用该宏进行声明,std::vector 、 std::list也内置对该宏的支持。
#include
#include
// 对std::deque进行声明,否则下面的会报错
Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(std::deque)
void someFunc()
{
std::deque container;
QVariant dequeVar = QVariant::fromValue(container); // 将std::deque放到QVariant,以便后期取出来
auto dequeCotainter = dequeVar.value< std::deque >();
if (!dequeCotainter.empty())
{
dequeCotainter.front(); // 取出第1个QFile指针对象
}
}
上述代码, 利用Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE宏对std::deque声明,这样就可以将std::deque保存到QVariant中了,如果不用该宏声明,则编译会报错。。
这个宏对关联容器进行声明,以便Qt的元类型能知道该关联容器,这样就可以将一个关联容器的实例
Qt的所有关联容器(如:QMap)都内置对该宏的支持,所以对于Qt的所有关联容器不需要用该宏进行声明,std::map也内置对该宏的支持。
#include
Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE(std::unordered_map)
void someFunc()
{
std::unordered_map unorderedMap;
unorderedMap.insert_or_assign(1, true); // 插入几个值到关联容器
unorderedMap.insert_or_assign(2, true);
QVariant var = QVariant::fromValue(unorderedMap); // 将关联容器存入QVariant对象
auto myCotainter = var.value< std::unordered_map >(); // 取出存入到QVariant中的关联容器对象
for (auto [id, isExist] : myCotainter) // 遍历输出关联容器的键值对,注意:这里用到了C++17
{
qDebug() << id << " ---- > " << isExist << "\r\n";
}
// ...
}
上述代码, 利用Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE宏对std::unordered_map声明,这样就可以将std::unordered_map保存到QVariant中了,如果不用该宏声明,则编译会报错。
这个宏向Qt的元系类型声明智能指针类型,以便让智能指针类型能让Qt元类型知道。该宏使智能指针对象能存入到QVariant对象中,前提是智能指针的T类型必须从QObject派生的。
注意:QWeakPointer、QSharedPointer 、 QPointer已经内置了对这个宏的支持,所以它们不需要利用该宏进行声明。下面的代码片段显示了该宏的用法:
#include
Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr)
void someFunc()
{
auto smart_ptr = std::make_shared();// 一个封装QFilel类型的智能指针
QVariant var = QVariant::fromValue(smart_ptr);// 将智能指针保存到QVariant中
// ...
if (var.canConvert())
{
QObject *sp = var.value();// 取出刚才保存的QFilel类型对象
qDebug() << sp->metaObject()->className(); // Prints 'QFile'.
}
}
上述代码, 利用Q_DECLARE_SMART_POINTER_METATYPE宏对智能指针声明,这样就可以将智能指针保存到QVariant中了,如果不用该宏声明,则编译会报错。