#(40):隱式數據共享
Qt 中許多 C++ 類使用了隱式數據共享技術,來最大化資源利用率和最小化拷貝時的資源消耗。當作為參數傳遞時,具有隱式數據共享的類即安全又高效。在數據傳遞時,實際上只是傳遞了數據的指針(這一切都是隱含幫你完成的),而只有在函數發生需要寫入的情況時,數據才會被拷貝(也就是通常所說的寫時復制)。本章我們將介紹有關隱式數據共享的相關內容,以便為恰當地使用前面所介紹的容器夯實基礎。
具有數據共享能力的類包含了一個指向共享數據塊的指針。這個數據塊包含了數據本身以及數據的引用計數。當共享對象創建出來時,引用計數被設置為 1。當新的對象引用到共享數據時,引用計數增加;當對象引用不再引用數據時,引用計數減少。當引用計數變為 0 時,共享數據被刪除。
在我們操作共享數據時,實際有兩種拷貝對象的方法:我們通常稱其為深拷貝和淺拷貝。深拷貝意味著要重新構造一個全新的對象;淺拷貝則僅僅復制引用,也就是上面所說的那個指向共享數據塊的指針。深拷貝對內存和 CPU 資源都是很昂貴的;淺拷貝則非常快速,因為它僅僅是設置一個新的指針,然后將引用計數加 1。具有隱式數據共享的對象,其賦值運算符使用的是淺拷貝來實現的。
這種隱式數據共享的好處是,程序不需要擁有不必要的重復數據,減少數據拷貝的需求。重復數據的代價是降低內存使用率(因為內存存儲了更多重復的數據)。通過數據共享,對象可以更簡單地作為值來傳遞以及從函數中返回。
隱式數據共享是在底層自動完成的,程序人員無需關心。這也是“隱式”一詞的含義。從 Qt4 開始,即使在多線程程序中,隱式數據共享也是起作用的。在很多人看來,隱式數據共享和多線程是不兼容的,這是由引用計數的實現方式決定的。但是,Qt 使用了原子性的引用計數來避免多線程環境下可能出現的執行順序打斷的行為。需要注意的是,原子引用計數并不能保證線程安全,還是需要恰當的鎖機制。這種觀點對所有類似的場合都是適用的。原子引用計數能夠保證的是,線程肯定操作自己的數據,線程自己的數據是安全的。總的來說,從 Qt4 開始,你可以放心使用隱式數據共享的類,即使在多線程環境下。
我們可以使用`QSharedData`和`QSharedDataPointer`類實現自己的隱式數據共享類。
當對象即將被修改,并且其引用計數大于 1 時,隱式數據共享自動將數據從共享塊中拿出。隱式共享類必須控制其內部數據,在任何修改其數據的函數中,將數據自動取出。
`QPen`使用了隱式數據共享技術,我們以`QPen`為例,看看隱式數據共享是如何起作用的:
~~~
void QPen::setStyle(Qt::PenStyle style)
{
detach(); // 從共享區取出數據
d->style = style; // 設置數據(更新)
}
void QPen::detach()
{
if (d->ref != 1) {
... // 執行深拷貝
}
}
~~~
凡是支持隱式數據共享的 Qt 類都支持類似的操作。用戶甚至不需要知道對象其實已經共享。因此,你應該把這樣的類當作普通類一樣,而不應該依賴于其共享的特色作一些“小動作”。事實上,這些類的行為同普通類一樣,只不過添加了可能的共享數據的優點。因此,你大可以使用按值傳參,而無須擔心數據拷貝帶來的性能問題。例如:
~~~
QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1; // p1 和 p2 共享數據
QPainter paint;
paint.begin(&p2); // 從此,p2 與 p1 分道揚鑣
paint.drawText(0,50, "Hi");
paint.end();
~~~
上例中,p1 和 p2 在`QPainter::begin()`一行之前都是共享數據的,直到這一語句。因為該語句開始,p2 就要被修改了。
注意,前面已經提到過,不要在使用了隱式數據共享的容器上,在有非 const STL 風格的遍歷器正在遍歷時復制容器。另外還有一點,對于`QList`或者`QVector`,我們應該使用`at()`函數而不是 [] 操作符進行只讀訪問。原因是 [] 操作符既可以是左值又可以是右值,這讓 Qt 容器很難判斷到底是左值還是右值,這意味著無法進行隱式數據共享;而`at()`函數不能作左值,因此可以進行隱式數據共享。另外一點是,對于`begin()`,`end()`以及其他一些非 const 遍歷器,由于數據可能改變,因此 Qt 會進行深復制。為了避免這一點,要盡可能使用`const_iterator`、`constBegin()`和`constEnd()`。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)