翻譯|使用教程|編輯:鮑佳佳|2020-10-26 13:39:08.137|閱讀 607 次
概述:您可能知道,Qt有一個元類型系統(tǒng),該系統(tǒng)提供有關(guān)類型的運行時動態(tài)信息。它可以將您的類型存儲在QVariant中,并在信號插槽系統(tǒng)中排成隊列,并在整個QML引擎中使用。在即將發(fā)布的Qt 6.0版本中,我們借此機會重新審視了它的基礎(chǔ)知識,并利用了C ++ 17為我們提供的功能。在下文中,我們將檢查這些更改,并說明它們?nèi)绾斡绊懩捻椖俊?/p>
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
Qt是一個跨平臺框架,通常用作圖形工具包,它不僅創(chuàng)建CLI應(yīng)用程序中非常有用。而且它也可以在三種主要的臺式機操作系統(tǒng)以及移動操作系統(tǒng)(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式設(shè)備,Android(Necessitas)和iOS的端口上運行。現(xiàn)在我們?yōu)槟闾峁┝嗣赓M的試用版。趕快點擊下載Qt最新試用版吧>>
慧都現(xiàn)推出“軟件國產(chǎn)化服務(wù)季”(點擊查看詳情),Qt正版授權(quán)獲取低價優(yōu)惠>>
您可能知道,Qt有一個元類型系統(tǒng),該系統(tǒng)提供有關(guān)類型的運行時動態(tài)信息。它可以將您的類型存儲在QVariant中,并在信號插槽系統(tǒng)中排成隊列,并在整個QML引擎中使用。在即將發(fā)布的Qt 6.0版本中,我們借此機會重新審視了它的基礎(chǔ)知識,并利用了C ++ 17為我們提供的功能。在下文中,我們將檢查這些更改,并說明它們?nèi)?何影響您的項目。
QMetaType更加了解您的類
在Qt 5中,QMetaType包含默認構(gòu)造一個類,復制它并銷毀它所必需的信息。此外,它知道如何將其保存到QDataStream以及從QDataStream加載它,并存儲了一些標志來描述它的各種屬性(例如,類型是否瑣碎,枚舉等)。另外,它將存儲該類型的QMetaObject(如果有的話)和一個數(shù)字ID,以標識該類型以及類型名稱。
最后,QMetaType包含用于比較某種(元)類型的對象,進行打印qDebug以及從一種類型轉(zhuǎn)換為另一種類型的功能。但是,您必須使用QMetaType::registerComparators()QMetaType中的和其他靜態(tài)寄存器函數(shù)才能真正利用該功能。這會將指向這些函數(shù)的指針放入相應(yīng)的注冊表中,基本上是從元類型ID到函數(shù)指針的映射。
在Qt 6中,我們做的第一件事就是擴展QMetaType中存儲的信息。現(xiàn)代C++已經(jīng)有將近10年的歷史了,所以是時候在QMetaType中存儲移動構(gòu)造函數(shù)的信息了。而且為了更好地支持過度對齊的類型,我們現(xiàn)在也存儲了你的類型的對齊要求。此外,我們認為注冊表有點笨拙。畢竟,我們?yōu)槭裁匆竽阏{(diào)用QMetaType::registerEqualsComparator(),而我們已經(jīng)可以通過簡單地查看類型來知道這一點?所以在 Qt 6 中,QMetaType::registerEqualsComparator、QMetaType::registerComparators、qRegisterMetaTypeStreamOperators 和 QMetaType::registerDebugStreamOperator 已經(jīng)被刪除。元類型系統(tǒng)會自動知道這些。這里的例外是QMetaType::registerConverterFunction。相反,元類型系統(tǒng)將自動知道這些信息。這里的離群值是QMetaType::registerEqualsComparatorQMetaType::registerComparatorsqRegisterMetaTypeStreamOperatorsQMetaType::registerDebugStreamOperatorQMetaType::registerConverterFunction。由于無法可靠地知道應(yīng)該使用哪些函數(shù)進行轉(zhuǎn)換,并且我們允許注冊基本上任意的轉(zhuǎn)換,因此該功能與Qt 5中的相同。
通過這些更改,我們還可以統(tǒng)一處理Qt內(nèi)部類型和用戶注冊的類型:這意味著例如QMetaType::compare現(xiàn)在可以使用int:
#include#include int main() { int i = 1; int j = 2; int result = 0; const bool ok = QMetaType::compare(&i, &j, QMetaType::Int, &result); if (ok) { // prints -1 as expected in Qt 6 qDebug() << result; } else { // This would get printed in Qt 5 qDebug() << "Cannot compare integer with QMetaType :-("; } }
QMetaType在編譯時知道您的類型
多虧了C++反思能力的各種進步,我們現(xiàn)在可以在編譯時從一個類型中獲得我們需要的所有信息--包括它的名字。在 Qt 中,我們使用了一個非常類似的方法,盡管對舊編譯器進行了某些擴展和變通。但比實現(xiàn)更有趣的是它對你意味著什么。首先,我們不需要通過以下兩種方式創(chuàng)建 QMetaType
QMetaType oldWay1 = QMetaType::fromName("KnownTypeName");
或者
QMetaType oldWay2(knownTypeID);
現(xiàn)在建議您使用以下命令創(chuàng)建QMetaType
QMetaType newWay = QMetaType::fromType();
如果你知道類型。其他方法仍然存在,當你在編譯時不知道類型時,這些方法是有用的。然而,fromType 避免了在運行時從 id/name 到 QMetaType 的一次查找。請注意,從 Qt 5.15 開始,你已經(jīng)可以使用 fromType 了,但它仍然會進行一次查找。此外,你不能復制QMetaType,這限制了它的實用性,使它更方便地傳遞類型id。然而,在 Qt 6 中,QMetaType 是可以復制的。
你現(xiàn)在可能會問,這對 Q_DECLARE_METATYPE 和 qRegisterMetaType 意味著什么。畢竟,如果我們可以在編譯時創(chuàng)建QMetaTypes,我們真的需要它們嗎?
我們先來看一個例子。
#include#include #include struct MyType { int i = 42; friend QDebug operator<<(QDebug dbg, MyType t) { QDebugStateSaver saver(dbg); dbg.nospace() << "MyType with i = " << t.i; return dbg; } }; int main() { MyType myInstance; QVariant var = QVariant::fromValue(myInstance); qDebug() << var; }
在Qt 5中,這將導致以下帶有g(shù)cc的錯誤消息(+有關(guān)實例化失敗的更多警告):
/usr/include/qt/QtCore/qmetatype.h: In instantiation of 'constexpr int qMetaTypeId() [with T = MyType]': /usr/include/qt/QtCore/qvariant.h:371:37: required from 'static QVariant QVariant::fromValue(const T&) [with T = MyType]' test.cpp:16:48: required from here /usr/include/qt/QtCore/qglobal.h:121:63: error: static assertion failed: Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system 121 | # define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message) | ^~~~~~~~~~~~~~~ /usr/include/qt/QtCore/qmetatype.h:1916:5: note: in expansion of macro 'Q_STATIC_ASSERT_X' 1916 | Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system");
這不是很好,但至少它告訴你需要使用 Q_DECLARE_METATYPE。然而,在Qt 6中,它可以很好地編譯,可執(zhí)行文件將打印QVariant(MyType, MyType with i = 42),正如人們所期望的那樣。不僅是QVariant,隊列連接也可以在沒有明確的Q_DECLARE_METATYPE的情況下工作。
現(xiàn)在,qRegisterMetaType呢?很不幸,這個還是需要的--假設(shè)你需要名稱到類型的查找。雖然一個QMetaType對象知道它被構(gòu)造出來的類型名稱,但全局名稱到元類型的映射只有在調(diào)用qRegisterMetaType之后才會發(fā)生。舉例說明一下。
struct Custom {};
const auto myMetaType = QMetaType::fromType();
// At this point, we do not know that the name "Custom" maps to the type Custom
int id = QMetaType::type("Custom"); Q_ASSERT(id == QMetaType::UnknownType);
qRegisterMetaType(); // from now on, the name -> type mapping works, too id = QMetaType::type("Custom") Q_ASSERT(id == myMetaType.id());
如果您使用舊樣式的signal-slot-connections或使用,仍然需要具有可用的類型映射名稱QMetaObject::invokeMethod。
在編譯時創(chuàng)建QMetaType的能力也允許我們將一個類的屬性的元類型存儲在它的QMetaObject中。這一改變主要是出于QML,這一改變給我們帶來了更高的性能,并且希望未來能減少內(nèi)存消耗。
. 不幸的是,這個變化對屬性聲明中使用的類型提出了新的要求。當moc看到它時,它的類型(或者如果它是一個指針/引用,指向的類型)需要完整。為了說明這個問題,請看下面的例子。
// example.h #includestruct S; class MyClass : public QObject { Q_OBJECT Q_PROPERTY(S* m_s MEMBER m_s); S *m_s = nullptr; public: MyClass(QObject *parent = nullptr) : QObject(parent) {} };
在Qt 5中,這沒有問題。但是,在Qt 6中,您可能會收到類似錯誤。
In file included from qt/qtbase/include/QtCore/qmetatype.h:1, from qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qobject.h:54, from qt/qtbase/include/QtCore/qobject.h:1, from qt/qtbase/include/QtCore/QObject:1, from example.h:1, from moc_example.cpp:10: qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h: In instantiation of 'struct QtPrivate::IsPointerToTypeDerivedFromQObject': qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:1073:63: required from 'struct QtPrivate::QMetaTypeTypeFlags' qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2187:40: required from 'QtPrivate::QMetaTypeInterface QtPrivate::QMetaTypeForType::metaType' qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2309:16: required from 'constexpr QtPrivate::QMetaTypeInterface* QtPrivate::qTryMetaTypeInterfaceForType() [with Unique = qt_meta_stringdata_MyClass_t; TypeCompletePair = QtPrivate::TypeAndForceComplete>]' qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2328:55: required from 'QtPrivate::QMetaTypeInterface* const qt_incomplete_metaTypeArray [1]> >' moc_example.cpp:102:1: required from here qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:766:23: error: invalid application of 'sizeof' to incomplete type 'S' 766 | static_assert(sizeof(T), "Type argument of Q_PROPERTY or Q_DECLARE_METATYPE(T*) must be fully defined"); | ^~~~~~~~~ make: *** [Makefile:882: moc_example.o] Error 1
注意靜態(tài)斷言,它告訴您必須完全定義類型。可以通過三種不同的方式解決此問題:
最后,在極少數(shù)情況下,您會故意使用不透明的指針。在這種情況下,您需要使用Q_DECLARE_OPAQUE_POINTER被使用。
盡管在我們的經(jīng)驗中具有不完整類型的屬性并不常見,但這肯定不是最佳選擇。此外,我們目前正在研究擴展工具支持,以至少自動檢測到此問題。
同樣,我們也嘗試為元對象系統(tǒng)已知的方法(信號、槽和Q_INVOKABLE函數(shù))的返回類型和參數(shù)創(chuàng)建元類型。這樣做的好處是可以避免在基于字符串的連接和QML引擎內(nèi)部進行一些名稱到類型的查找。然而,我們知道,在methdos中,不完整的類型是非常常見的。因此,對于方法,我們?nèi)匀挥幸粋€回退路徑,方法類型不需要完整,所以不需要在那里進行修改。如果可以的話,我們會在編譯時將元類型存儲在元對象中,但如果不能的話,我們會在運行時簡單的查找。不過有一個例外:如果你使用聲明式類型注冊宏(QML_ELEMENT和friends)來注冊你的類,我們甚至要求方法類型是完整的。在這種情況下,我們假設(shè)你公開的所有元方法實際上都是要在QML中使用的,因此你希望避免任何額外的運行時類型查找(注意這不會影響父類的元方法)。
QMetaType為QVariant提供動力
在我們重構(gòu)了QMetaType之后,我們也可以清理我們古老的QVariant類的內(nèi)部結(jié)構(gòu)。在 Qt 6 之前,QVariant 在內(nèi)部區(qū)分了用戶類型和內(nèi)置 Qt 類型,這使得該類變得非常復雜。QVariant也只能在其內(nèi)部緩沖區(qū)中存儲最大尺寸為sizeof(void *)和sizeof(double)的值。其他任何值都會被堆分配。在Qt 6中,其他任何東西都會包括常用的類,比如QString(因為QString在Qt 6中是3*sizeof(void *)大)。所以很明顯,我們必須為Qt 6重新設(shè)計QVariant。而我們也確實重新設(shè)計了它!我們設(shè)法簡化了它的內(nèi)部架構(gòu)。我們設(shè)法簡化了它的內(nèi)部架構(gòu),并使常見的用例變得更快。這包括修改 QVariant,使其現(xiàn)在在 SSO 緩沖區(qū)中存儲類型 <= 3*sizeof(void *) 。除了允許繼續(xù)存儲QStrings而不需要額外的分配,這也使得它可以存儲多態(tài)的PIMPL'd類型,如QImage3的QVariant中。這應(yīng)該證明對在data()中返回圖像的項目模型有利。
我們還在 QVariant 的現(xiàn)有方法中引入了一些行為變化。我們意識到沉默的行為改變是常見的bug來源,但認為當前的行為有足夠的bug傾向,所以才會有這樣的改變。以下是更改的內(nèi)容列表。
另一個值得注意的變化是,我們刪除了帶有QDataStream的QVariant的構(gòu)造函數(shù)。與其構(gòu)建包含QDataStream的QVariant(與其他構(gòu)造函數(shù)一致),不如嘗試從數(shù)據(jù)流加載QVariant。如果您確實想要這種行為,請operator>>改用。還請注意,QVariant::Type在Qt 6中已棄用了它及其相關(guān)方法(但仍然存在)。QMetaType::Type已添加使用的替代API 。這很有用,因為QVariant::type()只能返回QVariant::UserType用戶類型,而新的QVariant::typeId()總是返回具體的元類型。QVariant::userType這樣做(在Qt 5中已經(jīng)這樣做),但是從其名稱來看,它顯然也不適用于內(nèi)置類型。
最后,我們向QVariant添加了一些新功能:
結(jié)論與展望
Qt元類型系統(tǒng)的內(nèi)部是Qt的一部分,大多數(shù)用戶很少與之交互。但是,它是框架的核心,用于實現(xiàn)更多以用戶為中心的部分,例如QML,QVariant,QtDbus,Qt Remote Objects和ActiveQt。借助Qt 6中的更新,我們希望它在下一個十年中能夠像上一個一樣為我們服務(wù)。
說到下一個十年,您可能想知道元類型系統(tǒng)的未來將如何發(fā)展。除了我們已經(jīng)提到的使用它來增強QML引擎的計劃之外,我們還打算改善信號/插槽連接邏輯。這些更改都不應(yīng)該以任何方式影響您的代碼,而只是在幾個地方提高性能和內(nèi)存使用率。在更遠的將來,我們當然也將監(jiān)視C ++的發(fā)展,尤其是在靜態(tài)反射和元類方面。盡管我們預計moc不會很快消失,但我們確實考慮在它們廣泛可用后,將其某些功能替換為C ++功能。
提前預告一下,我們在Qt 6.0中又增加了一項新功能:QMetaContainer。在下一篇博文中我們將會告訴你它是什么有什么作用。
感謝您的閱讀,希望這篇文章能帶給你一定的幫助!如果這篇文章沒能滿足你的需求、點擊獲取更多文章教程!現(xiàn)在立刻下載Qt6免費試用吧!更多Qt類開發(fā)工具QtitanRibbon、QtitanChart、QtitanNavigation、QtitanDocking、QtitanDataGrid在線訂購現(xiàn)直降1000元,歡迎咨詢慧都獲取更多優(yōu)惠>>
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自: