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,如果取用,則各用各的,這樣節省內存。