一、導(dǎo)讀
在實(shí)際開發(fā)中,Qt中很多類可以直接作為函數(shù)參數(shù)傳遞,這是為什么?其背后的實(shí)現(xiàn)機(jī)制又是什么?這些都?xì)w功于隱式共享,本文基于Qt 5.15源碼,來聊聊隱式共享!
二、隱式共享簡介
Qt中的許多C++類使用隱式數(shù)據(jù)共享來提高資源使用并減少數(shù)據(jù)復(fù)制。當(dāng)這些類作為參數(shù)傳遞時(shí),因?yàn)橹粋鬟f一個(gè)指向數(shù)據(jù)的指針,并且只有當(dāng)函數(shù)寫入數(shù)據(jù)時(shí)數(shù)據(jù)才會(huì)被復(fù)制,即copy -on-write,隱式共享類是安全、高效的。
共享類由一個(gè)指向包含引用計(jì)數(shù)和數(shù)據(jù)的共享數(shù)據(jù)塊的指針組成。
當(dāng)創(chuàng)建共享對(duì)象時(shí),它將引用計(jì)數(shù)設(shè)置為1。每當(dāng)有新對(duì)象引用共享數(shù)據(jù)時(shí),引用計(jì)數(shù)就遞增,當(dāng)對(duì)象解引用共享數(shù)據(jù)時(shí),引用計(jì)數(shù)就遞減,當(dāng)引用計(jì)數(shù)變?yōu)榱銜r(shí),將刪除共享數(shù)據(jù)。
在處理共享對(duì)象時(shí),有兩種方法復(fù)制對(duì)象。也就是經(jīng)常談到的:深度拷貝和淺拷貝。深度拷貝意味著復(fù)制一個(gè)對(duì)象,淺拷貝是一個(gè)引用拷貝,也就是一個(gè)指向共享數(shù)據(jù)塊的指針。站在內(nèi)存和CPU角度,執(zhí)行一個(gè)深度拷貝可能是昂貴的操作,執(zhí)行淺拷貝則非???,因?yàn)闇\拷貝只涉及設(shè)置指針和增加引用計(jì)數(shù)。
注意:隱式共享對(duì)象的對(duì)象賦值(operator=())是使用淺拷貝實(shí)現(xiàn)的。
隱式共享的優(yōu)點(diǎn)是:
(1)程序不需要進(jìn)行不必要的數(shù)據(jù)復(fù)制操作,從而減少內(nèi)存的使用和多次執(zhí)行數(shù)據(jù)復(fù)制操作。
(2)可以很容易地被賦值。
(3)可以作為函數(shù)參數(shù)傳遞,并從函數(shù)中返回。
三、源碼角度分析隱式共享
隱式共享會(huì)自動(dòng)將對(duì)象從共享塊中分離出來,如果對(duì)象即將改變并且引用計(jì)數(shù)大于1,(這通常被稱為寫時(shí)復(fù)制或值語義。)
隱式共享類可以控制其內(nèi)部數(shù)據(jù),在任何修改其數(shù)據(jù)的成員函數(shù)中,它都會(huì)在修改數(shù)據(jù)之前自動(dòng)分離。(但是,需注意容器迭代器的特殊情況,后文將說明這一點(diǎn)?。?/p>
此處以QPen這個(gè)隱式共享類為例,從源碼角度分析QPen類是如何從更改內(nèi)部數(shù)據(jù)的成員函數(shù)中分離共享數(shù)據(jù)的。在Qt5.15源碼中用于描述QPen的文件為qpen_p.h、qpen.cpp、qpen.h三個(gè)文件,位于源碼路徑(/qtbase/src/gui/painting目錄)下。在QPen類定義中有一個(gè)detach():
實(shí)現(xiàn)如下:
detach()用于從共享pen數(shù)據(jù)中分離,以確保該pen只有一個(gè)引用數(shù)據(jù),如果多個(gè)pen共享公共數(shù)據(jù),這支pen將取消對(duì)數(shù)據(jù)的引用并獲得數(shù)據(jù)的副本;如果只有一個(gè)則返回,什么也不做。上述代碼中,QPenData實(shí)則是QPenPrivate的類型別名,用于描述QPen的數(shù)據(jù),定義如下(位于qpen_p.h文件中):
上述代碼分析了detach()函數(shù),下文以QPen的一個(gè)成員函數(shù)setStyle(Qt::PenStyle style)來描述,該函數(shù)實(shí)現(xiàn)如下:
從上述圖片所示,在setStyle()函數(shù)中,會(huì)使用detach()從公共數(shù)據(jù)中分離,然后在設(shè)置style成員。
綜上,如果Qt提供的類支持隱式共享,那么其源碼內(nèi)部實(shí)現(xiàn)都有對(duì)應(yīng)的數(shù)據(jù)管理機(jī)制,實(shí)現(xiàn)寫時(shí)復(fù)制。
四、隱式共享在開發(fā)中的使用
上述第二節(jié)描述了隱式共享的QPen類如何從更改內(nèi)部數(shù)據(jù)的成員函數(shù)中分離共享數(shù)據(jù)??珊喕癁橄率龃a片段:
voidQPen::setStyle(Qt::PenStyles) { detach();//從公共數(shù)據(jù)中分離 d->style=s;//設(shè)置style成員 } voidQPen::detach() { if(d->ref!=1){ ...//執(zhí)行深度拷貝 } }
所以,在開發(fā)中如果更改了對(duì)象,類將自動(dòng)與公共數(shù)據(jù)分離,甚至不會(huì)注意到這些對(duì)象是共享的。因此,可以將它們的單獨(dú)實(shí)例視為單獨(dú)的對(duì)象,它們始終作為獨(dú)立的對(duì)象。但在有些情況下可以共享數(shù)據(jù),因此可以將這些類的實(shí)例作為參數(shù)按值傳遞給函數(shù),而不必考慮復(fù)制開銷。
例如下列代碼:
QPixmapp1,p2; p1.load("image.bmp"); p2=p1;//p1和p2共享數(shù)據(jù) QPainterpaint; paint.begin(&p2);//將p2從p1中分離出來 paint.drawText(0,50,"iriczhao"); paint.end();
注:在使用stl風(fēng)格的迭代器時(shí),復(fù)制隱式共享容器(QMap,QList等)需要特別注意。
五、隱式共享迭代器問題
對(duì)于stl風(fēng)格的迭代器,在使用隱式共享類時(shí)應(yīng)格外注意。因?yàn)楫?dāng)?shù)髟谌萜魃霞せ顣r(shí),應(yīng)該避免復(fù)制容器。也就是迭代器指向一個(gè)內(nèi)部結(jié)構(gòu),如果復(fù)制一個(gè)容器,此時(shí)應(yīng)特別注意迭代器。例如以下代碼片段:
QLista,b; a.resize(100000);//創(chuàng)建一個(gè)大列表,里面填滿0。 QList ::iteratori=a.begin(); /*-------------------------------------------------------------*/ //使用迭代器i的錯(cuò)誤方法: b=a; /* 此時(shí)我們應(yīng)該注意迭代器i,因?yàn)樗鼘⒅赶蚬蚕頂?shù)據(jù) 如果我們執(zhí)行*i=4,那么我們將改變共享實(shí)例(兩個(gè)向量) 其行為不同于STL容器。在Qt中不能這樣做。 */ /*-------------------------------------------------------------*/ a[0]=5; /* 容器a現(xiàn)在與共享數(shù)據(jù)分離, 盡管i是容器a的迭代器,但是它現(xiàn)在作為容器b的迭代器工作。 這里的情況是(*i)==0。 */ b.clear();//現(xiàn)在迭代器i完全無效了。 intj=*i;//此時(shí)會(huì)出現(xiàn)未定義的行為! /* 來自b(i所指向的)的數(shù)據(jù)不見了。 這可以用STL容器(和(*i)==5)定義, 但是這時(shí)候使用QList,可能會(huì)崩潰。 */
總而言之:當(dāng)?shù)髟谌萜魃霞せ顣r(shí),應(yīng)該避免復(fù)制容器,所有的Qt容器類都應(yīng)該注意這一點(diǎn)。
六、隱式共享類和線程
在Qt中,對(duì)它的許多值類使用了隱式共享進(jìn)行了優(yōu)化,尤其是QImage和QString。從Qt 4開始,隱式共享類可以安全地跨線程復(fù)制。這些值類是完全可重入的。
一般情況下,都認(rèn)為隱式共享和多線程是不兼容的概念,因?yàn)橐糜?jì)數(shù)通常不允許這樣做。然而,Qt使用原子引用計(jì)數(shù)來確保共享數(shù)據(jù)的完整性,避免了引用計(jì)數(shù)器的潛在損壞。
但是需要注意原子引用計(jì)數(shù)不能保證線程安全性。在線程之間共享隱式共享類的實(shí)例時(shí),應(yīng)該適當(dāng)?shù)募渔i進(jìn)行鎖定。這一點(diǎn),與所有重入類(無論是否共享)相同。原子引用計(jì)數(shù)確實(shí)保證了一個(gè)線程在其自身、隱式共享類的本地實(shí)例上工作是安全的,所以,在開發(fā)中可以使用信號(hào)和槽函數(shù)機(jī)制在不同線程之間傳遞數(shù)據(jù),因?yàn)檫@可以在不需要顯式鎖定的情況下完成。
總而言之,Qt 中的隱式共享類實(shí)際上是隱式共享的。即使在多線程應(yīng)用程序中,也可以安全地使用它們,與普通的、非共享的、可重入的基于值的類一樣。
審核編輯:劉清
-
cpu
+關(guān)注
關(guān)注
68文章
10898瀏覽量
212574 -
C++語言
+關(guān)注
關(guān)注
0文章
147瀏覽量
7016 -
迭代器
+關(guān)注
關(guān)注
0文章
44瀏覽量
4330
原文標(biāo)題:懂Qt,隱式共享都知道嗎?
文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論