翻譯|使用教程|編輯:鮑佳佳|2020-10-26 13:39:08.137|閱讀 607 次
概述:您可能知道,Qt有一個元類型系統,該系統提供有關類型的運行時動態信息。它可以將您的類型存儲在QVariant中,并在信號插槽系統中排成隊列,并在整個QML引擎中使用。在即將發布的Qt 6.0版本中,我們借此機會重新審視了它的基礎知識,并利用了C ++ 17為我們提供的功能。在下文中,我們將檢查這些更改,并說明它們如何影響您的項目。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關鏈接:
Qt是一個跨平臺框架,通常用作圖形工具包,它不僅創建CLI應用程序中非常有用。而且它也可以在三種主要的臺式機操作系統以及移動操作系統(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式設備,Android(Necessitas)和iOS的端口上運行。現在我們為你提供了免費的試用版。趕快點擊下載Qt最新試用版吧>>
慧都現推出“軟件國產化服務季”(點擊查看詳情),Qt正版授權獲取低價優惠>>
您可能知道,Qt有一個元類型系統,該系統提供有關類型的運行時動態信息。它可以將您的類型存儲在QVariant中,并在信號插槽系統中排成隊列,并在整個QML引擎中使用。在即將發布的Qt 6.0版本中,我們借此機會重新審視了它的基礎知識,并利用了C ++ 17為我們提供的功能。在下文中,我們將檢查這些更改,并說明它們如何影響您的項目。
QMetaType更加了解您的類
在Qt 5中,QMetaType包含默認構造一個類,復制它并銷毀它所必需的信息。此外,它知道如何將其保存到QDataStream以及從QDataStream加載它,并存儲了一些標志來描述它的各種屬性(例如,類型是否瑣碎,枚舉等)。另外,它將存儲該類型的QMetaObject(如果有的話)和一個數字ID,以標識該類型以及類型名稱。
最后,QMetaType包含用于比較某種(元)類型的對象,進行打印qDebug以及從一種類型轉換為另一種類型的功能。但是,您必須使用QMetaType::registerComparators()QMetaType中的和其他靜態寄存器函數才能真正利用該功能。這會將指向這些函數的指針放入相應的注冊表中,基本上是從元類型ID到函數指針的映射。
在Qt 6中,我們做的第一件事就是擴展QMetaType中存儲的信息?,F代C++已經有將近10年的歷史了,所以是時候在QMetaType中存儲移動構造函數的信息了。而且為了更好地支持過度對齊的類型,我們現在也存儲了你的類型的對齊要求。此外,我們認為注冊表有點笨拙。畢竟,我們為什么要要求你調用QMetaType::registerEqualsComparator(),而我們已經可以通過簡單地查看類型來知道這一點?所以在 Qt 6 中,QMetaType::registerEqualsComparator、QMetaType::registerComparators、qRegisterMetaTypeStreamOperators 和 QMetaType::registerDebugStreamOperator 已經被刪除。元類型系統會自動知道這些。這里的例外是QMetaType::registerConverterFunction。相反,元類型系統將自動知道這些信息。這里的離群值是QMetaType::registerEqualsComparatorQMetaType::registerComparatorsqRegisterMetaTypeStreamOperatorsQMetaType::registerDebugStreamOperatorQMetaType::registerConverterFunction。由于無法可靠地知道應該使用哪些函數進行轉換,并且我們允許注冊基本上任意的轉換,因此該功能與Qt 5中的相同。
通過這些更改,我們還可以統一處理Qt內部類型和用戶注冊的類型:這意味著例如QMetaType::compare現在可以使用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++反思能力的各種進步,我們現在可以在編譯時從一個類型中獲得我們需要的所有信息--包括它的名字。在 Qt 中,我們使用了一個非常類似的方法,盡管對舊編譯器進行了某些擴展和變通。但比實現更有趣的是它對你意味著什么。首先,我們不需要通過以下兩種方式創建 QMetaType
QMetaType oldWay1 = QMetaType::fromName("KnownTypeName");
或者
QMetaType oldWay2(knownTypeID);
現在建議您使用以下命令創建QMetaType
QMetaType newWay = QMetaType::fromType();
如果你知道類型。其他方法仍然存在,當你在編譯時不知道類型時,這些方法是有用的。然而,fromType 避免了在運行時從 id/name 到 QMetaType 的一次查找。請注意,從 Qt 5.15 開始,你已經可以使用 fromType 了,但它仍然會進行一次查找。此外,你不能復制QMetaType,這限制了它的實用性,使它更方便地傳遞類型id。然而,在 Qt 6 中,QMetaType 是可以復制的。
你現在可能會問,這對 Q_DECLARE_METATYPE 和 qRegisterMetaType 意味著什么。畢竟,如果我們可以在編譯時創建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中,這將導致以下帶有gcc的錯誤消息(+有關實例化失敗的更多警告):
/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中,它可以很好地編譯,可執行文件將打印QVariant(MyType, MyType with i = 42),正如人們所期望的那樣。不僅是QVariant,隊列連接也可以在沒有明確的Q_DECLARE_METATYPE的情況下工作。
現在,qRegisterMetaType呢?很不幸,這個還是需要的--假設你需要名稱到類型的查找。雖然一個QMetaType對象知道它被構造出來的類型名稱,但全局名稱到元類型的映射只有在調用qRegisterMetaType之后才會發生。舉例說明一下。
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。
在編譯時創建QMetaType的能力也允許我們將一個類的屬性的元類型存儲在它的QMetaObject中。這一改變主要是出于QML,這一改變給我們帶來了更高的性能,并且希望未來能減少內存消耗。
. 不幸的是,這個變化對屬性聲明中使用的類型提出了新的要求。當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
注意靜態斷言,它告訴您必須完全定義類型。可以通過三種不同的方式解決此問題:
最后,在極少數情況下,您會故意使用不透明的指針。在這種情況下,您需要使用Q_DECLARE_OPAQUE_POINTER被使用。
盡管在我們的經驗中具有不完整類型的屬性并不常見,但這肯定不是最佳選擇。此外,我們目前正在研究擴展工具支持,以至少自動檢測到此問題。
同樣,我們也嘗試為元對象系統已知的方法(信號、槽和Q_INVOKABLE函數)的返回類型和參數創建元類型。這樣做的好處是可以避免在基于字符串的連接和QML引擎內部進行一些名稱到類型的查找。然而,我們知道,在methdos中,不完整的類型是非常常見的。因此,對于方法,我們仍然有一個回退路徑,方法類型不需要完整,所以不需要在那里進行修改。如果可以的話,我們會在編譯時將元類型存儲在元對象中,但如果不能的話,我們會在運行時簡單的查找。不過有一個例外:如果你使用聲明式類型注冊宏(QML_ELEMENT和friends)來注冊你的類,我們甚至要求方法類型是完整的。在這種情況下,我們假設你公開的所有元方法實際上都是要在QML中使用的,因此你希望避免任何額外的運行時類型查找(注意這不會影響父類的元方法)。
QMetaType為QVariant提供動力
在我們重構了QMetaType之后,我們也可以清理我們古老的QVariant類的內部結構。在 Qt 6 之前,QVariant 在內部區分了用戶類型和內置 Qt 類型,這使得該類變得非常復雜。QVariant也只能在其內部緩沖區中存儲最大尺寸為sizeof(void *)和sizeof(double)的值。其他任何值都會被堆分配。在Qt 6中,其他任何東西都會包括常用的類,比如QString(因為QString在Qt 6中是3*sizeof(void *)大)。所以很明顯,我們必須為Qt 6重新設計QVariant。而我們也確實重新設計了它!我們設法簡化了它的內部架構。我們設法簡化了它的內部架構,并使常見的用例變得更快。這包括修改 QVariant,使其現在在 SSO 緩沖區中存儲類型 <= 3*sizeof(void *) 。除了允許繼續存儲QStrings而不需要額外的分配,這也使得它可以存儲多態的PIMPL'd類型,如QImage3的QVariant中。這應該證明對在data()中返回圖像的項目模型有利。
我們還在 QVariant 的現有方法中引入了一些行為變化。我們意識到沉默的行為改變是常見的bug來源,但認為當前的行為有足夠的bug傾向,所以才會有這樣的改變。以下是更改的內容列表。
另一個值得注意的變化是,我們刪除了帶有QDataStream的QVariant的構造函數。與其構建包含QDataStream的QVariant(與其他構造函數一致),不如嘗試從數據流加載QVariant。如果您確實想要這種行為,請operator>>改用。還請注意,QVariant::Type在Qt 6中已棄用了它及其相關方法(但仍然存在)。QMetaType::Type已添加使用的替代API 。這很有用,因為QVariant::type()只能返回QVariant::UserType用戶類型,而新的QVariant::typeId()總是返回具體的元類型。QVariant::userType這樣做(在Qt 5中已經這樣做),但是從其名稱來看,它顯然也不適用于內置類型。
最后,我們向QVariant添加了一些新功能:
結論與展望
Qt元類型系統的內部是Qt的一部分,大多數用戶很少與之交互。但是,它是框架的核心,用于實現更多以用戶為中心的部分,例如QML,QVariant,QtDbus,Qt Remote Objects和ActiveQt。借助Qt 6中的更新,我們希望它在下一個十年中能夠像上一個一樣為我們服務。
說到下一個十年,您可能想知道元類型系統的未來將如何發展。除了我們已經提到的使用它來增強QML引擎的計劃之外,我們還打算改善信號/插槽連接邏輯。這些更改都不應該以任何方式影響您的代碼,而只是在幾個地方提高性能和內存使用率。在更遠的將來,我們當然也將監視C ++的發展,尤其是在靜態反射和元類方面。盡管我們預計moc不會很快消失,但我們確實考慮在它們廣泛可用后,將其某些功能替換為C ++功能。
提前預告一下,我們在Qt 6.0中又增加了一項新功能:QMetaContainer。在下一篇博文中我們將會告訴你它是什么有什么作用。
感謝您的閱讀,希望這篇文章能帶給你一定的幫助!如果這篇文章沒能滿足你的需求、點擊獲取更多文章教程!現在立刻下載Qt6免費試用吧!更多Qt類開發工具QtitanRibbon、QtitanChart、QtitanNavigation、QtitanDocking、QtitanDataGrid在線訂購現直降1000元,歡迎咨詢慧都獲取更多優惠>>
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自: