QVariant可以支持任意类型的存储,功能有点类似于boost中的any,但是实现却完全不同,它同样是类型安全的,而且代价更小
要熟悉QVariant,我们需要先看下qRegisterMetaType的源码
先简单看下如何使用QVariant
void CTestVariant::Test()
{
int iNewType = qRegisterMetaType<CVariantHelp>("CVariantHelp");// 先注册一个QT认识的类型
CVariantHelp a1;
QVariant aa(iNewType, &a1);// 告诉QVariant类型及值。这样QVariant就记下了,下次取的时候判断会判断类型,这样实现类型安全
{
QVariant bb = aa;
}
// 取值
int itype = aa.userType();
assert(itype == iNewType);
CVariantHelp* pB = reinterpret_cast<CVariantHelp*>(aa.data());
}
qRegisterMetaType源码,传入一个T的类型,typename值
template <typename T>
int qRegisterMetaType(const char *typeName)
{
typedef void*(*ConstructPtr)(const T*); // 定义一个构造函数,接受参数T*的值,返回void*
// 这是一个全局函数,T决定了不同的T会编译出不同的全局函数,cptr保存类型为T的qMetaTypeConstructHelper的全局函数
ConstructPtr cptr = qMetaTypeConstructHelper<T>;
typedef void(*DeletePtr)(T*);
DeletePtr dptr = qMetaTypeDeleteHelper<T>;
return QMetaType::registerType(typeName, reinterpret_cast<QMetaType::Destructor>(dptr),
reinterpret_cast<QMetaType::Constructor>(cptr));
}
看下qMetaTypeConstructHelper这个全局函数,比较简单,接受一个T*的值,调用T的默认构造函数(无参数)或则有参数的拷贝构造函数
template <typename T>
void *qMetaTypeConstructHelper(const T *t)
{
if (!t)
return new T;
return new T(*static_cast<const T*>(t));
}
qMetaTypeDeleteHelper类似,是析构函数,代码可以想出来就是delete T了
QMetaType::registerType函数,接受参数typename,T的析构函数,构造函数
并且把构造函数,析构函数都转换成了下面两个类型,还是利用了void*,所以任何类型T的构造函数,析构函数都可以转换成void*这种
typedef void (*Destructor)(void *);
typedef void *(*Constructor)(const void *);
QMetaType::registerType的代码比较复杂,里面有些宏定义,展开也不好跟踪源码,只是根据部分代码推测
Qt定义了一个QT自己内部的所有类型及typename的数组
static const struct { const char * typeName; int type; } types[] = {
/* All Core types */
{"void", QMetaType::Void},
{"bool", QMetaType::Bool},
{"int", QMetaType::Int},
。。
{"qreal", QMetaTypeId2<qreal>::MetaType},
{0, QMetaType::Void}
}
注意,这个里面的是QMetaType,而实际上QVariant也有一个type,这两个类型的值基本是一样的,可能历史原因吧,有两套值
Qt会现在types这个数组里面查找name,是否有,如果没有(最后一个0也是个标志位),再到customeTypes里面找,依然没有找到,就往ct里面append一个,ct就是存储custometypes的vector了
int QMetaType::registerType(const char *typeName, Destructor destructor,
Constructor constructor)
{
QVector<QCustomTypeInfo> *ct = customTypes();
。。。
QWriteLocker locker(customTypesLock());
int idx = qMetaTypeType_unlocked(normalizedTypeName); // 在types以及customtpye中找typename
if (!idx) {
QCustomTypeInfo inf;
inf.typeName = normalizedTypeName;
inf.constr = constructor;
inf.destr = destructor;
idx = ct->size() + User;
ct->append(inf); // 如果没有找到,添加一个。ct是类型为QCustomeTpyeInfo的一个QVector.里面需要typename以及它的构造,析构函数
return idx;
}
好了,这基本上就是qRegisterMetaType的一个思想了,存储了Qt自带的类型(types这个全局变量的数组(types是static类型的,即内部链接的),自定义类型在custometypes里面。
再往下看QVariant的构造函数,接受一个自定义类型,一个值
有两个重要的变量d.type保存类型,值保存在d.data.shared;中
QVariant::QVariant(int typeOrUserType, const void *copy)
{ create(typeOrUserType, copy); d.is_null = false; }
void QVariant::create(int type, const void *copy)
{
d.type = type;
handler->construct(&d, copy);
}
另外也看下默认的两个QT内置类型的构造就更加清晰了
QVariant::QVariant(int val)
{ d.is_null = false; d.type = Int; d.data.i = val; }
QVariant::QVariant(uint val)
{ d.is_null = false; d.type = UInt; d.data.u = val; }
type赋值比较简单,shared的赋值稍微复杂点呢,继续看construct,最后调用到QMetaType了,返回了一个void*
void *QMetaType::construct(int type, const void *copy)
{
...
得到构造函数
const QVector<QCustomTypeInfo> * const ct = customTypes();
QReadLocker locker(customTypesLock());
if (type < User || !ct || ct->count() <= type - User)
return 0;
if (ct->at(type - User).typeName.isEmpty())
return 0;
constr = ct->at(type - User).constr;
调用
return constr(copy); // copy即传入的值,QVariant内部会调用构造函数,通过拷贝构造,保存下用户传入的值
}
然后将void*赋值给shared,x就是上面传入的&d了
void *ptr = QMetaType::construct(x->type, copy);
if (!ptr) {
x->type = QVariant::Invalid;
} else {
x->is_shared = true;
x->data.shared = new QVariant::PrivateShared(ptr);
}
另外此处利用了一个共享指针,其它QVariant使用,不会重新分配新的内存
QVariant bb = aa;的调用过程,只是指针的赋值,然后ref加1
QVariant::QVariant(const QVariant &p)
: d(p.d)
{
if (d.is_shared) {
d.data.shared->ref.ref(); // ref加1
} else if (p.d.type > Char && p.d.type < QVariant::UserType) {
handler->construct(&d, p.constData());
d.is_null = p.d.is_null;
}
}
析构函数,会将计数减1(defef)并且判断是否为0,为0则调用clear清空共享内存
QVariant::~QVariant()
{
if ((d.is_shared && !d.data.shared->ref.deref()) || (!d.is_shared && d.type > Char && d.type < UserType))
handler->clear(&d);
}
这基本上就是QVariant的全部了,总结一下
1、用户自定义需要先注册一个类型,即使用qRegisterMetaType,注册到QT的一个Vector中
2、QVariant里面会new一个用户自定义类型的内存,并调用拷贝构造函数,QVariant自身的赋值会使用共享内存管理
所以用户可以传入一个临时变量地址,如果用户传入的是一个指针,这个指针需要用户自己析构,改变这个指针的值,并不会改变QVariant,因为是两个不同的空间了
而如果QVariant a1=b1(b1是QVariant),改变b1的值会改变a1的。因为这样用的是shared指针
初看2以为是对的,验证发现不准确,改变b1并没有改变a1的值,细看发现这里面有QT使用了个小技巧,要取b1的值然后改变时,会调用data函数
CVariantHelp* pBTemp = reinterpret_cast<CVariantHelp*>(b1.data());
pBTemp->j_ = 99;
而data的实现会调用detach将shared分离
void* QVariant::data()
{
detach();
return const_cast<void *>(constData());
}
void QVariant::detach()
{
if (!d.is_shared || d.data.shared->ref == 1)
return;
Private dd;
dd.type = d.type;
handler->construct(&dd, constData());
if (!d.data.shared->ref.deref())
handler->clear(&d);
d.data.shared = dd.data.shared;
}
如果不取用data,两个用一个shared,如果取用,则各用各的,这样节省内存。