真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Qt/C++借助QVariant實現(xiàn)可存儲通用類型的容器-創(chuàng)新互聯(lián)

1. 背景

在項目開發(fā)過程中,我們可能會遇到這么一種場景:某個或某幾個軟件組件可以產(chǎn)生許多不同類型的數(shù)據(jù),無論是出于性能的考慮,或者是接口簡潔性的考慮,這些數(shù)據(jù)需要被一次性塞到一個類似于數(shù)據(jù)庫的數(shù)據(jù)容器中。而這個容器將會被眾多接收者使用,它們各自從容器中取出自己感興趣的內(nèi)容進行處理。此外,不同的接收者可能運行在不同線程中的,這個容器還需要支持復(fù)制操作,使得這些接受者在訪問時互不干擾。最后,為了便于調(diào)試以及數(shù)據(jù)的離線分析,這個容器還應(yīng)該支持序列化與反序列化。

成都服務(wù)器托管,成都創(chuàng)新互聯(lián)提供包括服務(wù)器租用、綿陽電信機房、帶寬租用、云主機、機柜租用、主機租用托管、CDN網(wǎng)站加速、域名與空間等業(yè)務(wù)的一體化完整服務(wù)。電話咨詢:028-86922220

簡而言之,該場景的需求如下:

  1. 需要一個能夠同時存儲多種數(shù)據(jù)類型的容器;
  2. 該容器需要提供拷貝的功能;
  3. 該容器需要支持序列化與反序列化。

我們姑且將滿足以上需求的容器稱為可存儲通用類型的容器。本文假設(shè)項目中使用了Qt庫,在此基礎(chǔ)上,為實現(xiàn)這種容器提供了一種可行的思路,并給出實現(xiàn)這種容器的要點。

2. 設(shè)計思路 2.1. 為單個數(shù)據(jù)項選擇合適的容器

首先,我們需要解決的問題就是如何存儲各種類型的單個數(shù)據(jù)項。我們根據(jù)需求逐一分析:

  1. 需求1:為了滿足需求1,最簡單的實現(xiàn)思路就是使用void *,存儲任意類型的數(shù)據(jù)。
  2. 需求2:由于需要支持容器的復(fù)制,這意味著容器中的每個數(shù)據(jù)項目也應(yīng)該逐一被復(fù)制到新的容器中,那么需求1中提到的void *將無法滿足該需求。這是因為void *擦除了類型信息,在數(shù)據(jù)項目需要被復(fù)制時,我們已經(jīng)無從得知該數(shù)據(jù)的長度以及其他信息了。那能否用void *+ size來存儲這些內(nèi)容呢?對于基礎(chǔ)數(shù)據(jù)類型以及其數(shù)組,例如int、float以及它們數(shù)組等,這種方案是可以應(yīng)付的。但是,一旦數(shù)據(jù)項目中使用了容器類,例如std::vector,因為通常情況下,它們實際的存儲空間是在堆中申請的,我們不能直接通過其指針來訪問實際的數(shù)據(jù)內(nèi)容,因此void *+ size的方案不可行。C++ 17中引入了std::any用于存儲單個任意類型的對象(包括自定義類型),這似乎是一個很不錯的選擇。
  3. 需求3:std::any本身并沒有直接支持序列化與反序列化功能,如果每個基礎(chǔ)類型都需要自己再實現(xiàn)一次序列化與反序列,那實在太折騰了。既然我們都已經(jīng)基于Qt庫進行開發(fā)了,那是不是可以直接利用Qt中已經(jīng)提供的各種序列化與反序列化功能(通過QDataStream)。我們知道,Qt Core模塊中的QVariant類提供了與std::any相似的功能,并且更加強大。更幸運地是,在Serializing Qt Data Types列表中,QVarant赫然在列。

綜上,為了避免重復(fù)造輪子,我們選擇了QVariant作為存儲單個數(shù)據(jù)項目的容器。該類支持對象之間的拷貝,因此,選擇QVariant讓我們可以同時滿足3個需求,前提是我們使用的數(shù)據(jù)類型都是Qt內(nèi)置的類型。對于自定義類型,我們還需要做一些簡單的開發(fā)工作,這將在后面的小節(jié)中介紹。

在確定了單個數(shù)據(jù)項目的容器之后,我們還需要選擇一個容器來存儲多個數(shù)據(jù)項目,這里假設(shè)使用std::unordered_map<>,那么,我們只需要按照如下方式定義:

std::unordered_mapcontainer;

我們就得到了一個簡易的,可同時存儲多種數(shù)據(jù)類型的關(guān)聯(lián)容器。接收者通過key值便能夠以正確的方式解析真實的數(shù)據(jù)類型。

由于QVaraiant是實現(xiàn)通用類型的容器的核心,需要重點介紹一下。

2.1.1. QVariant

QVariant類的作用類似于Qt數(shù)據(jù)類型的聯(lián)合,它還能支持用戶自定義的類型。

QVariant對象一次保存一個type()的單個值。(有些type()是多值的,例如字符串列表。)我們可以使用convert()將其轉(zhuǎn)換為不同的類型,使用眾多toT()函數(shù)之一(例如,toSize())獲取其值,并使用canConvert()檢查該類型是否可以轉(zhuǎn)換為某個特定類型。

名為toT()(例如toInt()、toString())的方法是const方法。如果想要獲取實際存儲的類型,這些函數(shù)會返回存儲對象的副本(注意,這里返回的是副本,所以無法通過返回的值來修改QVariant存儲的內(nèi)容)。如果想使用可以從存儲的類型生成的類型,toT()會復(fù)制并轉(zhuǎn)換,并保持對象本身不變。如果請求了一個不能從存儲的類型生成的類型,結(jié)果取決于該類型;有關(guān)的詳細信息,請參見函數(shù)文檔。

下列是官方的實例代碼,它們闡述了如何使用QVariant

QDataStream out(...);
QVariant v(123);                // The variant now contains an int
int x = v.toInt();              // x = 123
out<< v;                       // Writes a type tag and an int to out
v = QVariant("hello");          // The variant now contains a QByteArray
v = QVariant(tr("hello"));      // The variant now contains a QString
int y = v.toInt();              // y = 0 since v cannot be converted to an int
QString s = v.toString();       // s = tr("hello")  (see QObject::tr())
out<< v;                       // Writes a type tag and a QString to out
...
QDataStream in(...);            // (opening the previously written stream)
in >>v;                        // Reads an Int variant
int z = v.toInt();              // z = 123
qDebug("Type is %s",            // prints "Type is int"
        v.typeName());
v = v.toInt() + 100;            // The variant now hold the value 223
v = QVariant(QStringList());

甚至可以將QListQMap的值存入到一個QVariant對象中,因此,我們可以輕松地構(gòu)造任意類型的復(fù)雜的數(shù)據(jù)結(jié)構(gòu),并將其存入到QVariant中。這是非常強大和通用的,但可能比在標準數(shù)據(jù)結(jié)構(gòu)中存儲相應(yīng)類型的內(nèi)存和速度效率低。

QVariant還支持null的概念,在這種情況下,我們可以定義一個沒有值的類型。但是,請注意,QVariant類型只有在設(shè)置了值后才能進行強制轉(zhuǎn)換。例如:

QVariant x, y(QString()), z(QString(""));
x.convert(QVariant::Int);
// x.isNull() == true
// y.isNull() == true, z.isNull() == false

除了支持內(nèi)置的類型枚舉中的類型之外,QVariant可以通過擴展以支持其他類型。如何實現(xiàn)讓QVariant可識別的類型,參看讓QVariant支持自定義類型的存儲。

2.2. 讓QVariant支持自定義類型的存儲

首先, 我們需要確保自定義的類型滿足QMetaType的所有要求。換句話說,它必須提供:

  • 一個公有的默認構(gòu)造函數(shù);
  • 一個公有的拷貝構(gòu)造函數(shù);
  • 一個公有的析構(gòu)函數(shù)。

例如,我們有一個MyData自定義類型:

class MyData {public:
    uint32_t dataId;
    std::vectordata;

    MyData() = default;
    ~MyData() = default;
    MyData(const MyData &) = default;
    MyData & operator=(const MyData &) = default;
};

在此基礎(chǔ)上,我們還需要做一些額外的簡單操作,否則,Qt的類型系統(tǒng)將無法理解如何存儲、檢索和序列化該類的實例。例如,我們將無法在QVariant中存儲MyData值。

Qt中負責(zé)定制類型的類是QMetaType。為了讓這個類能識別這個類型,我們在定義MyData的頭文件中調(diào)用這個類的Q_DECLARE_METATYPE()宏,代碼如下:

Q_DECLARE_METATYPE(MyData);

這使得將MyData對象存儲在QVariant對象中并在以后檢索成為可能。官方文檔也給出了一個示例代碼,請參見自定義類型示例。

2.3. 讓自定義類型支持序列化與反序列化

如前文提到,我們將使用QDataStreamQVarant進行序列化與反序列化。對于自定義類型,我們則需要實現(xiàn)自定義的序列化與反序列化函數(shù)。在這個例子中,我們需要實現(xiàn)兩個MyData的友元函數(shù):

class MyData {public:
    uint32_t dataId;
    std::vectordata;

    MyData() = default;
    ~MyData() = default;
    MyData(const MyData &) = default;
    MyData & operator=(const MyData &) = default;

    friend QDataStream & operator<<(QDataStream & stream, const MyData &data);
    friend QDataStream & operator>>(QDataStream & stream, MyData &data);
};

QDataStream & operator<<(QDataStream & stream, const MyData &data)
{stream<< data.dataId;
    stream<< static_cast(data.data.size());
    for (const auto & val : data.data) {stream<< val;
    }
    return stream;
}

QDataStream & operator>>(QDataStream & stream, MyData &data)
{stream >>data.dataId;
    quint64 count = 0;
    stream >>count;
    uint32_t val = 0;
    for (int i = 0; i< count; i++) {stream >>val;
        data.data.push_back(val);
    }
    return stream;
}

在完成以上代碼后,我們便能支持自定義類型的序列化了。代碼如下:

QDataStream stream(...);

MyData data;
data.dataId = 10086;
data.data = {1, 0, 0, 8, 6};
QVariant var;
var.setValue(data);
stream<< var;

若此時我們執(zhí)行反序列化是可行的,因為在setValue()時,內(nèi)部幫我們在元對象系統(tǒng)中注冊了這個自定義類型。但如果是我們重新運行程序,反序列化就會失效。反序列化時需要重新創(chuàng)建出這個自定義類型的對象,因為此時元對象系統(tǒng)中并不清楚該對象的信息,所以它不知道如何在運行時處理自定義類型對象的創(chuàng)建和銷毀。

要在運行時創(chuàng)建對象,需要通過調(diào)用qRegisterMetaType()模板函數(shù)將自定義類型注冊到元對象系統(tǒng)。此后,除了讓反序列化時元對象系統(tǒng)可識別之外,這也使該類型可用于隊列式(queued)信號-槽通信。我們需要在使用該類型的反序列化之前調(diào)用它,本例子中,我們選擇在MainWindow的構(gòu)造函數(shù)中進行注冊:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{ui->setupUi(this);
    qRegisterMetaType();
}

此后,我們便可以使用如下的方式進行反序列化了:

QDataStream stream(...);

QVariant var;
stream >>var;
auto data = qvariant_cast(var);
// do something on data...
3. Reference
  1. QVariant Class
  2. Creating Custom Qt Types
  3. QMetaType Class

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


分享題目:Qt/C++借助QVariant實現(xiàn)可存儲通用類型的容器-創(chuàng)新互聯(lián)
分享地址:http://weahome.cn/article/dpicis.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部