Qt 中許多 C++ 類使用了隱式數據共享技術,來最大化資源利用率和最小化拷貝時的資源消耗。看成爲參數傳遞時,具備隱式數據共享的類即安全又高效。在數據傳遞時,實際上只是傳遞了數據的指針(這一切都是隱含幫你完成的),而只有在函數發生須要寫入的狀況時,數據纔會被拷貝(也就是一般所說的寫時複製)。本章咱們將介紹有關隱式數據共享的相關內容,以便爲恰當地使用前面所介紹的容器夯實基礎。安全
具備數據共享能力的類包含了一個指向共享數據塊的指針。這個數據塊包含了數據自己以及數據的引用計數。當共享對象建立出來時,引用計數被設置爲 1。當新的對象引用到共享數據時,引用計數增長;當對象引用再也不引用數據時,引用計數減小。當引用計數變爲 0 時,共享數據被刪除。多線程
在咱們操做共享數據時,實際有兩種拷貝對象的方法:咱們一般稱其爲深拷貝和淺拷貝。深拷貝意味着要從新構造一個全新的對象;淺拷貝則僅僅複製引用,也就是上面所說的那個指向共享數據塊的指針。深拷貝對內存和 CPU 資源都是很昂貴的;淺拷貝則很是快速,由於它僅僅是設置一個新的指針,而後將引用計數加 1。具備隱式數據共享的對象,其賦值運算符使用的是淺拷貝來實現的。函數
這種隱式數據共享的好處是,程序不須要擁有沒必要要的重複數據,減小數據拷貝的需求。重複數據的代價是下降內存使用率(由於內存存儲了更多重複的數據)。經過數據共享,對象能夠更簡單地做爲值來傳遞以及從函數中返回。性能
隱式數據共享是在底層自動完成的,程序人員無需關心。這也是「隱式」一詞的含義。從 Qt4 開始,即便在多線程程序中,隱式數據共享也是起做用的。在不少人看來,隱式數據共享和多線程是不兼容的,這是由引用計數的實現方式決定的。可是,Qt 使用了原子性的引用計數來避免多線程環境下可能出現的執行順序打斷的行爲。須要注意的是,原子引用計數並不能保證線程安全,仍是須要恰當的鎖機制。這種觀點對全部相似的場合都是適用的。原子引用計數可以保證的是,線程確定操做本身的數據,線程本身的數據是安全的。總的來講,從 Qt4 開始,你能夠放心使用隱式數據共享的類,即便在多線程環境下。線程
咱們可使用QSharedData
和QSharedDataPointer
類實現本身的隱式數據共享類。指針
當對象即將被修改,而且其引用計數大於 1 時,隱式數據共享自動將數據從共享塊中拿出。隱式共享類必須控制其內部數據,在任何修改其數據的函數中,將數據自動取出。code
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()
。