翻譯|行業(yè)資訊|編輯:鮑佳佳|2020-12-04 10:12:10.090|閱讀 1159 次
概述:Qt 6具有很多新功能。我們添加的最令人興奮的功能之一是將QML和Qt Quick綁定的概念帶回到Qt的核心,并允許從C ++使用它。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
Qt是一個(gè)跨平臺(tái)框架,通常用作圖形工具包,它不僅創(chuàng)建CLI應(yīng)用程序中非常有用。而且它也可以在三種主要的臺(tái)式機(jī)操作系統(tǒng)以及移動(dòng)操作系統(tǒng)(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式設(shè)備,Android(Necessitas)和iOS的端口上運(yùn)行。現(xiàn)在我們?yōu)槟闾峁┝嗣赓M(fèi)的試用版。趕快點(diǎn)擊下載Qt6最新試用版>>
工具推薦:
Qt 6具有很多新功能。我們添加的最令人興奮的功能之一是將QML和Qt Quick綁定的概念帶回到Qt的核心,并允許從C ++使用它。
Qt 5中的綁定
讓我們首先回顧一下Qt 5中屬性綁定的工作方式。在那里,綁定支持僅限于Qt Quick。這是一個(gè)非常簡(jiǎn)單的示例:
import QtQuick 2.15 Rectangle { height: width border.width: height/10 }
這樣做的目的是在一個(gè) Rectangle 對(duì)象上設(shè)置兩個(gè)綁定。第一個(gè)綁定確保Rectangle永遠(yuǎn)是正方形。第二個(gè)綁定將邊框?qū)挾仍O(shè)置為高度的10%。然后,Qt中的QML引擎確保這些關(guān)系將被保留,并在Rectangle的寬度改變時(shí)自動(dòng)調(diào)整高度和邊框?qū)挾取?
這種綁定的機(jī)制是使Qt Quick中的UI定義大多以聲明的方式編寫。綁定表達(dá)式(綁定的右側(cè))可以任意復(fù)雜,包含對(duì)其他對(duì)象屬性的引用,甚至調(diào)用其他方法。
在Qt 5的生命周期中,我們已經(jīng)看到,綁定使代碼的表現(xiàn)力更強(qiáng),并刪除了很多需要編寫的膠水代碼。所以,在Qt 6中,我們的目標(biāo)是允許作為一個(gè)C++開發(fā)者也能使用這種機(jī)制。
讓我們看看如何在C++中表達(dá)同樣的關(guān)系。下面是我們希望這樣一個(gè)Rectangle如何寫成一個(gè)C++類。
class Rectangle { public: Property<int> width; Property<int> height; Property<int> border; Rectangle() { height.setBinding(width); border.setBinding([this]() { return height / 10; }); } };
這定義了一個(gè)具有3個(gè)屬性的Rectangle類:width,height和border。然后,構(gòu)造函數(shù)設(shè)置兩個(gè)綁定,一個(gè)綁定將高度綁定到寬度,另一個(gè)綁定將邊框綁定到高度的10%。
當(dāng)我們著手進(jìn)行Qt 6時(shí),我們面臨的問題是我們是否可以以高效且高效的方式來實(shí)現(xiàn)這一目標(biāo)。
綁定系統(tǒng)的目標(biāo)
除了良好且易于使用的語法外,系統(tǒng)還需要滿足其他一些要求。
讓我們看一下新系統(tǒng)的實(shí)施方式以及我們?nèi)绾螌?shí)現(xiàn)上述目標(biāo)。
簡(jiǎn)單實(shí)施
讓我們從最簡(jiǎn)單的方法開始,以實(shí)現(xiàn)支持我們正在尋找的功能的 QProperty類:
template <typename T> class QProperty { std::function<T()> binding = nullptr; T data; public: T value() const { if (binding) return binding(); return data; } void setValue(const T &newValue) { if (binding) binding = nullptr; data = newValue; } void setBinding(std::function<T> b) { binding = b; } };
上面的實(shí)現(xiàn)可能是實(shí)現(xiàn)支持綁定的QProperty類的最簡(jiǎn)單方法。它基本上包含了屬性數(shù)據(jù)和一個(gè)有可能為空的綁定的函數(shù)指針。每當(dāng)在屬性上設(shè)置了一個(gè)綁定,如果設(shè)置了一個(gè)綁定,屬性獲取器將總是執(zhí)行綁定來檢索值。
然而這種實(shí)現(xiàn)有幾個(gè)嚴(yán)重的缺點(diǎn),使得它不適合按原樣使用。最明顯的一個(gè)缺點(diǎn)就是性能會(huì)非常差,特別是當(dāng)綁定依賴于其他屬性,而這些屬性本身也有綁定的時(shí)候。每次調(diào)用getter時(shí)都要評(píng)估這些綁定,會(huì)造成嚴(yán)重的性能問題。更糟糕的是,這可能會(huì)導(dǎo)致應(yīng)用程序崩潰或死鎖,萬一一個(gè)綁定以某種方式引用回自己。
立即和延遲的綁定評(píng)估
所以我們確實(shí)需要一個(gè)更高級(jí)一點(diǎn)的設(shè)計(jì)。基本上有兩種可能的方法來避免每次調(diào)用setter時(shí)計(jì)算綁定的值。這兩種方法都涉及到將結(jié)果值緩存在數(shù)據(jù)中。此外,我們還需要記住一個(gè)綁定所依賴的屬性。
Qt Quick在Qt 5中做的就是即時(shí)綁定評(píng)估,這意味著每當(dāng)一個(gè)屬性被改變,我們就會(huì)立即觸發(fā)對(duì)所有依賴這個(gè)屬性的綁定的重新評(píng)估。這個(gè)系統(tǒng)的缺點(diǎn)是,它可能會(huì)導(dǎo)致不必要的綁定表達(dá)式的評(píng)估。一個(gè)例子是一個(gè)被綁定為width*height的屬性區(qū)域。如果寬度和高度都被分配了新的值,面積就會(huì)被計(jì)算兩次,盡管只有第二個(gè)結(jié)果會(huì)被使用。
因此,在 Qt 6 中,我們使用了延遲綁定評(píng)估。這意味著我們遞歸地將所有依賴于屬性的綁定標(biāo)記為 dirty。然后,屬性獲取器檢查該 dirty 標(biāo)志,如果它為真,則重新評(píng)估綁定表達(dá)式,然后將結(jié)果存儲(chǔ)在數(shù)據(jù)中并清除 dirty 標(biāo)志。
這就是QProperty現(xiàn)在的簡(jiǎn)化視圖。
template <typename T> class QProperty { T val; QPropertyBindingData d; public: T value() const { if (d.hasBinding()) d.evaluateIfDirty(this); d.registerWithCurrenlyEvaluatingBinding(); return this->val; } void setValue(const T &t) { d.removeBinding(); if (this->val == t) return; this->val = t; notify(); } };
這里發(fā)生的事情是,getter檢查我們是否有一個(gè)綁定,如果有,則重新評(píng)估它。之后,作為第二步,它將自己與任何可能正在評(píng)估的綁定進(jìn)行注冊(cè)。setValue()與之前相當(dāng)類似。如果新舊值相同,我們就會(huì)快捷設(shè)置器,以避免這種情況下的綁定重新評(píng)估。如果設(shè)置了新的值,我們就調(diào)用notify(),而notify()又會(huì)將所有依賴于這個(gè)屬性的綁定標(biāo)記為dirty。
還有很多細(xì)節(jié)需要我們?nèi)ソ鉀Q。例如,依賴注冊(cè)使用線程本地存儲(chǔ)來了解當(dāng)前正在評(píng)估的綁定。如果你想知道所有的細(xì)節(jié),請(qǐng)看Qt 6中QProperty的實(shí)現(xiàn)。
通知和變更處理程序
除了設(shè)置綁定外,QProperty還允許為屬性注冊(cè)變化處理程序。使用QProperty的onValueChanged()或subscribe()方法,可以注冊(cè)一個(gè)回調(diào),每當(dāng)屬性的底層值發(fā)生變化時(shí),這個(gè)回調(diào)就會(huì)被調(diào)用。
當(dāng)屬性的值通過調(diào)用setter而改變時(shí),或者當(dāng)屬性的綁定因?yàn)樗囊粋€(gè)依賴關(guān)系改變而被標(biāo)記為dirty時(shí),回調(diào)將被調(diào)用。
QObjects屬性系統(tǒng)中的綁定支持回顧上面概述的目標(biāo),你可能已經(jīng)注意到,QProperty的實(shí)現(xiàn)并沒有解決Qt 6中綁定引擎的所有目標(biāo)。它的性能確實(shí)非常好(見下面進(jìn)一步的性能數(shù)據(jù)),而且它只是在沒有使用綁定時(shí)增加了一個(gè)小的開銷。這個(gè)開銷主要是在getter中檢查我們是否有綁定和對(duì)當(dāng)前正在評(píng)估的綁定進(jìn)行TLS查找,在setter中快速檢查依賴關(guān)系。
但它確實(shí)給每個(gè)屬性帶來了不可忽視的額外4到8個(gè)字節(jié)的內(nèi)存開銷,而且它也沒有和QObject中現(xiàn)有的屬性系統(tǒng)集成。接下來我們來看看這些是如何解決的。
雖然現(xiàn)在的QProperty可以獨(dú)立使用,也可以在任何類中使用,但我們希望有一個(gè)能與QObject中現(xiàn)有的屬性系統(tǒng)無縫集成、兼容的東西。這個(gè)系統(tǒng)是圍繞QObject的屬性建立的,只是在類定義中擁有一個(gè)setter和一個(gè)getter作為公共成員。這如何用數(shù)據(jù)來支持有些無關(guān)緊要。
為了支持這些屬性的數(shù)據(jù)綁定,我們需要看看如何調(diào)整QProperty的想法來適應(yīng)這里。
我們最終得到的是一個(gè)實(shí)現(xiàn)屬性的QObject公共API的簡(jiǎn)單擴(kuò)展。
class MyObject : public QObject { Q_PROPERTY(int x GET x SET setX BINDABLE bindableX) // the line below was “int xData;” in Qt 5 Q_OBJECT_BINDABLE_PROPERTY(MyObject, int, xData) public: int x() { return xData; } void setX(int x) { xData = x; } QBindable<int> bindableX() { return &xData; } };
紅色標(biāo)記的部分是Qt 6中的新內(nèi)容。正如你所看到的那樣,在Qt 6中,使一個(gè)屬性可綁定所需的改動(dòng)相對(duì)較少。簡(jiǎn)單的用于存儲(chǔ)數(shù)據(jù)的 "int xData; "被一個(gè)實(shí)現(xiàn)綁定邏輯的宏所取代,即QProperty作為一個(gè)獨(dú)立類所做的一些事情。此外,我們?cè)黾恿艘粋€(gè)新的bindableX()方法,該方法返回一個(gè)QBindable<int>,并在Q_PROPERTY宏中告訴元對(duì)象系統(tǒng)。
QBindable<T>是一個(gè)輕量級(jí)接口,它提供了QProperty中也有的附加功能。它允許設(shè)置和檢索綁定并注冊(cè)通知。例如,在MyObject的x屬性上設(shè)置一個(gè)綁定可以通過調(diào)用來實(shí)現(xiàn)。
myObject-> bindableX()。setBinding([otherObject](){ return otherObject-> x()+ otherObject-> width(); }
使用這些宏以及我們知道QObject正在使用它的事實(shí)有兩個(gè)優(yōu)點(diǎn)。與QProperty不同,Q_OBJECT_BINDABLE_PROPERTY不會(huì)增加任何內(nèi)存開銷。宏實(shí)現(xiàn)的對(duì)象的大小與要存儲(chǔ)的數(shù)據(jù)的大小相同。這是通過將綁定數(shù)據(jù)移到整個(gè)QObject實(shí)例的公共數(shù)據(jù)結(jié)構(gòu)(按需分配)中來實(shí)現(xiàn)的。
它使查找綁定的速度稍微慢一些,但是另一方面,由于在QObject中具有按需數(shù)據(jù)結(jié)構(gòu),因此我們可以避免對(duì)當(dāng)前正在執(zhí)行的綁定進(jìn)行TLS查找。這也意味著,當(dāng)不使用綁定程序?qū)etter和getter進(jìn)行指針查找和比較時(shí),可以減少運(yùn)行時(shí)開銷。
讓我們快速看一下它是如何實(shí)現(xiàn)的。為了允許在QObject屬性中使用綁定,上面的Q_OBJECT_BINDABLE_PROPERTY宏擴(kuò)展為兩件事。首先,它在對(duì)象內(nèi)部定義了一個(gè)靜態(tài)成員函數(shù):
static constexpr size_t _qt_property_cData_offset() { return offsetof(MyObject, xData); }
然后,此方法允許被用作下一行中定義的QObjectBindableProperty實(shí)例的模板參數(shù):
QObjectBindableProperty <MyObject,int,MyObject :: _qt_property_cData_offset> xData;
這樣做的結(jié)果是,我們現(xiàn)在有了一個(gè)方法,可以從屬性數(shù)據(jù)的this指針計(jì)算出擁有屬性數(shù)據(jù)的QObject的this指針。這個(gè)東西我們又用來從QObject中檢索一個(gè)QBindingStorage指針。這個(gè)指針可能是空的,在這種情況下,我們有快速路徑,在這個(gè)對(duì)象上沒有使用綁定。否則,我們?cè)赒BindingStorage中查找QProperty內(nèi)置的QPropertyBindingData。一旦我們檢索到一個(gè)有效的綁定數(shù)據(jù)的指針,QObjectBindableProperty基本上就會(huì)進(jìn)行和QProperty一樣的操作。
向后兼容
像Qt 5一樣使用changeSignal()作為通知實(shí)現(xiàn)的屬性將繼續(xù)像以前一樣工作。這意味著它們可以與Qt Quick中的綁定一起使用,但不能與C ++中的綁定一起使用。但是,他們還將繼續(xù)使用即時(shí)綁定評(píng)估。
為了獲得新系統(tǒng)的全部好處,您應(yīng)該考慮將綁定支持添加到您自己的屬性中。這將使它們可以從C ++綁定,并且在大多數(shù)情況下將開始使用延遲綁定評(píng)估。向QObject的現(xiàn)有屬性添加綁定支持是100%向后兼容的。
Qt 6本身的大多數(shù)屬性仍未移植為也不支持新的綁定引擎。我們計(jì)劃在Qt 6.1和6.2中實(shí)現(xiàn)這一點(diǎn)。
基準(zhǔn)數(shù)據(jù)
我們先來看看不使用綁定時(shí)屬性讀寫的性能。這一點(diǎn)很重要,因?yàn)槲覀儾幌MF(xiàn)有代碼出現(xiàn)較大的回歸。為了測(cè)試,我們看一個(gè)整數(shù)屬性。這測(cè)試的是最壞的情況,因?yàn)樽x寫一個(gè)整數(shù)的速度是最快的,因此結(jié)果將最清楚地顯示任何增加的項(xiàng)。
讀 | 寫 | |
舊樣式屬性 | 3,8ns | 7.2ns |
QObjectBindableProperty(無通知) | 4,3ns | 4,5ns |
QObjectBindableProperty(信號(hào)已更改) | 4,3ns | 8.2ns |
QProperty | 9,1ns | 5,4ns |
表中顯示了結(jié)果,測(cè)試了幾個(gè)案例。第一個(gè)是用Qt 5的方式實(shí)現(xiàn)的一個(gè)屬性,有g(shù)etter、setter和一個(gè)變化的信號(hào)。接下來的兩行使用Q_OBJECT_BINDABLE_PROPERTY使屬性可綁定。在一種情況下,我們沒有添加Qt 5風(fēng)格的改變信號(hào)(因?yàn)樾孪到y(tǒng)并不依賴它們),另一種情況下,為了向后兼容,仍然發(fā)出一個(gè)改變信號(hào)。最后一行顯示了QProperty的表現(xiàn)。
正如你所看到的,我們對(duì)于getter的速度慢了10%左右(但請(qǐng)注意,舊式屬性的getter擴(kuò)展為一個(gè)包含三條指令的函數(shù)調(diào)用)。對(duì)于最常見的屬性沒有變化信號(hào)的情況,setter要快40%。QProperty稍微慢一些,因?yàn)樗枰鲆粋€(gè)TLS查找。
對(duì)于基于QString的屬性來說,差異會(huì)小得多,所以我們可以得出結(jié)論,在沒有使用綁定的情況下,我們成功地添加了對(duì)綁定的支持,而沒有顯著的開銷。
現(xiàn)在讓我們看看綁定的性能如何。為此,我們使用一個(gè)整數(shù)屬性與另一個(gè)整數(shù)屬性的簡(jiǎn)單直接綁定。我們有兩個(gè)測(cè)試案例,一個(gè)案例是我們連續(xù)設(shè)置第一個(gè)屬性,然后讀取第二個(gè)屬性的值。在第二個(gè)案例中,我們只對(duì)第一個(gè)屬性進(jìn)行寫入,但從不讀取第二個(gè)屬性。每一個(gè)案例,我們都分成兩個(gè)子案例,一個(gè)是我們通過QObjects通用屬性接口(setProperty()和property())讀寫值,一個(gè)是我們使用C++ setter和getter。
然后,我們?yōu)榕f式屬性以及支持直接綁定的新屬性運(yùn)行這些測(cè)試用例。
讓我們從一個(gè)用QML定義的綁定開始,并像在Qt 5中一樣進(jìn)行評(píng)估。
訪問方式 | 寫讀 | 只寫 | 寫讀 | 只寫 |
setProperty /屬性 | 設(shè)置器/獲取器 | |||
舊樣式屬性 | 370ns | 240ns | 130ns | 130ns |
QObjectBindableProperty(無通知) | 370ns | 110ns | 120ns | 14ns |
QObjectBindableProperty(信號(hào)已更改) | 410ns | 120ns | 140ns | 25ns |
QProperty | 440ns | 130ns | 130ns | 10ns |
雖然Qt 5中的QML為某些選定的屬性提供了一些快捷方式,但某些屬性可能最終會(huì)通過QObject的通用屬性接口進(jìn)行訪問。該表第一行中的數(shù)字反映了我們?cè)赒t 5.15中可以獲得的最壞情況和最好情況。
其他行顯示了我們?cè)赒t 6中可以獲得的性能。您會(huì)看到,在每次寫入之后都進(jìn)行一次讀取的情況與Qt 5中的時(shí)間大致相同。這是可以預(yù)期的,因?yàn)槲覀冃枰獙?duì)Qt 5進(jìn)行處理。同樣的工作量。但是在所有情況下,在有多次寫入的情況下,在我們?cè)俅涡枰搶傩缘闹抵埃孪到y(tǒng)在一定程度上擊敗了舊系統(tǒng)。
讓我們看一下在C ++中設(shè)置綁定時(shí)會(huì)發(fā)生什么。由于舊的屬性系統(tǒng)無法做到這一點(diǎn),因此我們?cè)诖颂幫ㄟ^將lambda連接到設(shè)置了新值的更改信號(hào)來對(duì)其進(jìn)行仿真。應(yīng)該注意的是,這不能替代綁定,因?yàn)樗緹o法擴(kuò)展到更復(fù)雜的綁定表達(dá)式,并且需要大量的手動(dòng)設(shè)置才能捕獲所有依賴項(xiàng)。
訪問方式 | 寫讀 | 只寫 | 寫讀 | 只寫 |
setProperty /屬性 | 設(shè)置器/獲取器 | |||
舊樣式屬性 | 230ns | 120ns | 29ns | 30ns |
QObjectBindableProperty(無通知) | 250ns | 100ns | 35ns | 12ns |
QObjectBindableProperty(信號(hào)已更改) | 280ns | 120ns | 51ns | 22ns |
QProperty | 300ns | 120ns | 48ns | 9ns |
最左邊的兩列主要供參考,并與上表進(jìn)行比較。在C ++中,幾乎永遠(yuǎn)不會(huì)通過基于字符串的通用屬性API訪問屬性。相反,最右邊的兩列反映了C ++中的典型用法。
可以看出,綁定系統(tǒng)的性能幾乎與兩個(gè)舊樣式屬性之間的直接信號(hào)/插槽連接一樣好。鑒于它要靈活得多,并且可以自動(dòng)捕獲所有依賴項(xiàng)(需要使用信號(hào)/插槽手動(dòng)聲明),因此數(shù)量很多。
您還可以看到,使用setter和getter的基于C ++的綁定比Qt 5.15中QML中定義的綁定快3-10倍。展望未來,我們計(jì)劃通過探索將QML中定義的綁定表達(dá)式編譯為C ++然后進(jìn)行匯編的方式來利用這一事實(shí)。
結(jié)論
Qt 5中的綁定引擎使Qt Quick如此成功。有了Qt 6,我們現(xiàn)在已經(jīng)把這個(gè)引擎從Qt Quick中移到了Qt的核心,并且讓它也能為C++開發(fā)者所用。
在這樣做的同時(shí),我們成功地實(shí)現(xiàn)了比 Qt 5 中的性能的顯著提高。盡管如此,仍未完成工作,因?yàn)閹熘械拇蠖鄶?shù)屬性仍需要移植到新系統(tǒng)上。
好了這就是今天的內(nèi)容了,如果今天的文章未解決你的需求,點(diǎn)擊獲取更多文章教程。不要忘了在評(píng)論與我們分享您的想法和建議。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自: