QVariant源碼解析

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章