1. 程式人生 > >Qt之隱式共享及記憶體分配策略

Qt之隱式共享及記憶體分配策略

一、隱式共享簡介

(來源《Qt5開及發例項》第三版)

隱式共享又稱回寫複製(copy no write)。當兩個物件共享一部分資料時(通過淺拷貝實現資料塊共享),如果資料不變,則不進行資料的複製。而當某個物件需要改變資料時,則執行深拷貝。

程式在處理共享物件時,使用深拷貝和淺拷貝這兩種方法複製物件。所謂深拷貝,就是生成物件的一個完整的複製品;而淺拷貝則是一個引用複製(如僅複製指向共享資料的指標)。顯然,執行一個深拷貝的代價是比較昂貴的,要佔用更多的記憶體和CPU資源;而淺拷貝的效率則是很高,它僅需要設定一個指向共享資料塊的指標及修改引用計數的值。

隱式共享可以降低對記憶體和CPU資源的使用率,提高程式的執行效率。它使得在函式中(如引數、返回值)使用值傳遞更有效率。

QString類採用隱式共享技術,將深拷貝和淺拷貝有機的結合起來。

下面通過一個例子來具體介紹隱式共享是如何工作的。

Qstring str1="data";             //初始化一個內容為“data”的字串
Qstring str2=str1;         //(a)
str2[3]='e';                //(b)
str2[0]='f';                //(c)
str1=str2;                  //(d)

其中,

(a)QString str2=str1:將該字串物件str1賦值給另一個字串str2(由QString的複製建構函式),此時str2=“data”。在對str2賦值的時候,將發生一次淺拷貝,導致兩個QString物件都指向同一個資料結構。該資料結構除了儲存字串“data”外,還儲存了一個引用計數器,以記錄字串資料的引用次數。在這裡,因為str1和str2指向同一個資料結構,所以計數器的值為2。

(b)str2[3]='e':對QString物件str2的修改,將會導致一次深拷貝,使得str2物件指向一個新的、不同於str1所指的資料結構(該資料結構的引用計數1,因為只有str2指向這個資料結構),同時修改原來的str1指向的資料結構,設定它的引用計數為1(此時,只有QString物件str1指向該資料結構)。繼而在這個str2指向的、新的資料結構上完全資料的修改。引用計數為1意味著這個資料沒有被共享。此時str2=“data”,str=“data”。

(c)str[0]='f':進一步對QString物件str2進行修改,但這個操作不會引起任何形式的複製,因為str2指向的資料結構沒有被共享。此時,str2=“fate”,str1=“data”。

(d)str1=str2:將str2賦值給str1.此時,str1將它指向的資料結構的引用計數器的值修改為0,也就是說,沒有QString物件再使用這個資料結構了。因此,str1指向的資料結構將會從記憶體中釋放掉。該操作的結果是,QString物件str1和str2都指向字串為“fate”的資料結構,該資料結構的引用計數為2.

Qt Implicit Sharing (隱式共享)

以下內容來源網路 Implicit Sharing 隱式共享 Many C++ classes in Qt use implicit data sharing to maximize resource usage and minimize copying. Implicitly shared classes are both safe and efficient when passed as arguments, because only a pointer to the data is passed around, and the data is copied only if and when a function writes to it, i.e., copy-on-write. Qt中的很多C++類通過使用隱式資料共享來最大化資源的使用效率和最小化複製的資源耗費。將隱 式共享類作為引數傳遞不僅安全而且效率高,因為在這個過程中只有指向這個資料的指標被傳遞, 並且當且僅當有函式對這個資料進行寫操作時,才會對該資料進行復制。 Overview Implicit Sharing in Detail List of Classes Overview A shared class consists of a pointer to a shared data block that contains a reference count and the data. 一個共享類包括一個指向一個共享資料塊的指標,共享資料塊由資料和對共享資料進行引用的數目。 When a shared object is created, it sets the reference count to 1. The reference count is incremented whenever a new object references the shared data, and decremented when the object dereferences the shared data. The shared data is deleted when the reference count becomes zero. 當一個共享物件被建立時,共享資料的引用數目會被設定為1.無論何時,只要有一個新的物件引用該 共享資料,該共享資料對應的這個引用數目都會加一,相反,若有物件不再引用該共享資料時,其對 應的引用數目將會減一。當共享資料的引用數目為0時,該共享資料就會被撤銷掉。 When dealing with shared objects, there are two ways of copying an object. We usually speak about deep and shallow copies. A deep copy implies duplicating an object. A shallow copy is a reference copy, i.e. just a pointer to a shared data block. Making a deep copy can be expensive in terms of memory and CPU. Making a shallow copy is very fast, because it only involves setting a pointer and incrementing the reference count. 當處理共享物件時,有兩種方法來複制一個物件。我們常常將其稱為深拷貝和淺拷貝。深拷貝會隱式的 複製一個物件。而錢拷貝只是引用的複製,即只是一個指向共享資料塊的指標的複製。做一次深拷貝 會佔用很大的記憶體和CPU資源。然而,做一次淺拷貝則很快,因為那僅僅需要處理指標的設定和引用數目 的增加。 Object assignment (with operator=()) for implicitly shared objects is implemented using shallow copies. 隱式共享物件的賦值是通過淺拷貝來實現的。 The benefit of sharing is that a program does not need to duplicate data unnecessarily, which results in lower memory use and less copying of data. Objects can easily be assigned, sent as function arguments, and returned from functions. 共享的好處是程式不需要對資料進行不必要的複製,這樣就降低了記憶體的使用並且減少了資料的複製。 物件可以容易地被複制,作為函式的引數傳遞,作為函式的返回值。 Implicit sharing takes place behind the scenes; the programmer does not need to worry about it. Even in multithreaded applications, implicit sharing takes place, as explained in Threads and Implicitly Shared Classes. 隱式共享發生在場景背後,程式設計師不必擔心它。甚至在多執行緒應用程式中,隱式共享也會像在多執行緒和隱式共享類中描述 的那樣發生。 When implementing your own implicitly shared classes, use the QSharedData and QSharedDataPointer classes. 但要實現你自己的隱式共享類時,可以使用QSharedData和QSharedDataPointer這兩個類。 Implicit Sharing in Detail 隱式共享的細節 Implicit sharing automatically detaches the object from a shared block if the object is about to change and the reference count is greater than one. (This is often called copy-on-write or value semantics.) 如果物件即將改變或引用數目大於一時,隱式共享會自動從共享的塊上脫離著個物件。(這通常被稱為寫時拷貝或值語義) An implicitly shared class has total control of its internal data. In any member functions that modify its data, it automatically detaches before modifying the data. 一個隱式共享類有他內部資料的總控制。在任何成員函式中修改它的資料,它將在資料修改之前,自動脫離共享資料塊。 The QPen class, which uses implicit sharing, detaches from the shared data in all member functions that change the internal data. QPen 類就是一個隱式共享類。在所有修改內部資料的成員函式中,它會脫離共享資料。 Code fragment: 程式碼片段: void QPen::setStyle(Qt::PenStyle style) { detach();           // detach from common data  從共享資料中脫離 d->style = style;   // set the style member        設定style資料成員 } void QPen::detach() { if (d->ref != 1) { ...             // perform a deep copy  進行一次深拷貝 } } List of Classes 類列表 The classes listed below automatically detach from common data if an object is about to be changed. The programmer will not even notice that the objects are shared. Thus you should treat separate instances of them as separate objects. They will always behave as separate objects but with the added benefit of sharing data whenever possible. For this reason, you can pass instances of these classes as arguments to functions by value without concern for the copying overhead. 下面的類列表中,如果他們的物件將要改變時,物件將會從共享資料脫離。程式設計師甚至沒注意到物件是共享資料的。那樣你應該 把他們單獨的例項當作單獨的物件。他們總是表現的像單獨的物件一樣,但是隻要有可能就依然存在新增的資料共享的好處。這 樣,你可以通過值將這些類的例項作為引數傳遞給函式,而不必關心複製的總開銷。 Example: 例如: QPixmap p1, p2; p1.load("image.bmp"); p2 = p1;                        // p1 and p2 share data   //p1 和 p2 共享資料。 QPainter paint; paint.begin(&p2);               // cuts p2 loose from p1   //p2 自動脫離共享資料。 paint.drawText(0,50, "Hi"); paint.end(); In this example, p1 and p2 share data until QPainter::begin() is called for p2, because painting a pixmap will modify it. 在這個例子中,p1 和 p2 共享資料,直到為p2呼叫函式QPainter::begin() ,因為畫一副圖將會改變這幅圖。 Warning: Do not copy an implicitly shared container (QMap, QVector, etc.) while you are iterating over it using an non-const STL-style iterator. 警告:當你使用一個非常量STL風格的迭代器在隱式共享容器上進行迭代時,不要複製這個隱式共享的容器(QMap, QVector, 等等)

二、記憶體分配策略

Qstring在一個連續的記憶體塊中儲存字串資料。當字串的長度不斷的增長時Qstring需要重新分配記憶體空間,以便有足夠的空間儲存增加的字串。QString使用記憶體分配策略如下。

每次分配4個字元空間,直到大小為20.

在20~4084之間,QString分配的記憶體塊大小以兩倍的速度增長。

從4084開始,每次以2048個字元大小(4096位元組,即4KB)的步長增長。