天天看點

C++ string的COW和SSO

還記得書本上的關于深拷貝和淺拷貝的問題嗎?

如果自己寫的一個類TestClass,采用的是預設的構造函數,或者采用自定義的構造函數(但是沒有實作深拷貝)。那麼下面的代碼:

class TestClass

{

public:

char * p;

};

TestClass a;

TestClass  b(a);

TestClass  c = a;

上訴都是淺拷貝,也就是a.p == b.p == c.p;如果要想深拷貝,必須由你自己用代碼實作。

STL中string的情況呢?

首先來看看下面語句的語義(也就是你這樣寫實際上是想要達到什麼目的,或者是大家一看就知道的想要達到的目的):

string str1;

string str2(str1);

string str3 = str1;

上訴的三句實際上是想實作的什麼呢?答案是深拷貝。實際上隻要是采取上訴的指派操作符或者是拷貝構造函數,使用者都是想實作的是深拷貝。那麼作為STL string的開發實作者就必須要實作string拷貝構造函數和=操作符的深拷貝。

但是這裡string做了優化,做到對記憶體管理的優化。

這裡不同版本的string采用了不同的方法:COW和SSO

看如下代碼:

string str1("123456");

string str2(str1);

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());   //1 step

str1[0] = '1';

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());   //2 step

如果string采取的是COW的實作方式,那麼1 step輸出的str1.c_str()和str2.c_str()的值相等,說明str1和str2共享了字元串數組這部分的記憶體空間。(因為我的VS2010采用的是SSO方式,是以這裡不能貼出實際的實驗結果)。

但是當str1和str2中有任何一方的資料發生更改的時候,就會發生重新申請記憶體的操作和拷貝操作(因為兩個string資料不一樣了,不能再共享了)。

Copy On Write(COW)相當于是把申請記憶體和拷貝操作往後拖延了,直到不得不這樣做時才這樣做。這種方法是把導緻配置設定支援的耗時操作往後延遲的方法。可能導緻多個延遲操作在後面的某一時間同時需要統一完成(多個string資料同時發生改變),那麼會導緻後面的某個時間點會出現比較大的累計延遲。可以說有利也有弊。

再看看如下的代碼:

string     str1("123456789654464444444461322222222222222222222222222222222222222363333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222");

string str2(str1);

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());

str1[0] = '1';

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());

運作結果如下:

C++ string的COW和SSO

這是字元串比較長的時候的string申請記憶體空間的情況。很顯然字元串數組(str1.c_str())的位址和string對象的位址差别較大,前者處于堆空間,後者處于函數的棧空間。這種情況一般就是我們所認識的深拷貝了,我們自己寫的類的深拷貝也就是這種情況。

再看下面的代碼:

string str1("123456");

string str2(str1);

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());

str1[0] = '1';

printf("%d***************%d +++++++ %d***************%d\n", &str1, str1.c_str(), &str2, str2.c_str());

運作結果是:

C++ string的COW和SSO

從結果可以看出&str1和str1.c_str()的值比較接近,他們都屬于函數的棧空間。這就說明此時的string内部的字元串數組沒有去堆上申請空間,而是足夠小的字元串直接存在了對象本身的棧記憶體中。是以這種情況str1和str2是沒有共享記憶體的,所有的資料都在各自的對象中。這在語義上是深拷貝,但是和我們自己實作的深拷貝不一樣。這就是有的STL string版本實作的(small string optimization)SSO方式。VS2010就是采用的SSO方式。