STL的寫時拷貝(Copy-On-Write)

來源:互聯網
上載者:User

原作地址:

http://hi.baidu.com/jakisou/blog/item/255e9cd66f16a72a06088b20.html

 

1、概念

 

Scott Meyers在《More Effective C++》 中舉了個例子,不知你是否還記得?在你還在上學的時候,你的父母要你不要看電視,而去複習功課,於是你把自己關在房間裡,做出一副正在複習功課的樣子,其 實你在乾著別的諸如給班上的某位女生寫情書之類的事,而一旦你的父母出來在你房間要檢查你是否在複習時,你才真正撿起課本看書。這就是“拖延戰術”,直到 你非要做的時候才去做。

 

當然,這種事情在現實生活中時往往會出事,但其在編程世界中搖身一變,就成為了最有用的技術,正如C++中的可以隨處聲明變數的特點一樣,Scott Meyers推薦我們,在真正需要一個儲存空間時才去聲明變數(分配記憶體),這樣會得到程式在運行時最小的記憶體花銷。執行到那才會去做分配記憶體這種比較耗時的工作,這會給我們的程式在運行時有比較好的效能。必竟,20%的程式運行了80%的時間。

 

當 然,拖延戰術還並不只是這樣一種類型,這種技術被我們廣泛地應用著,特別是在作業系統當中,當一個程式運行結束時,作業系統並不會急著把其清除出記憶體,原 因是有可能程式還會馬上再運行一次(從磁碟把程式裝入到記憶體是個很慢的過程),而只有當記憶體不夠用了,才會把這些還駐留記憶體的程式清出。

 

寫時才拷貝(Copy-On-Write)技術,就是編程界“懶惰行為”——拖延戰術的產物。舉個例子,比如我們有個程式要寫檔案,不斷地根據網路傳來的資料寫,如果每一次fwrite或是fprintf都要進行一個磁碟的I/O操 作的話,都簡直就是效能上巨大的損失,因此通常的做法是,每次寫檔案操作都寫在特定大小的一塊記憶體中(磁碟緩衝),只有當我們關閉檔案時,才寫到磁碟上 (這就是為什麼如果檔案不關閉,所寫的東西會丟失的原因)。更有甚者是檔案關閉時都不寫磁碟,而一直等到關機或是記憶體不夠時才寫磁碟,Unix就是這樣一個系統,如果非正常退出,那麼資料就會丟失,檔案就會損壞。

 

呵呵,為了效能我們需要冒這樣大的風險,還好我們的程式是不會忙得忘了還有一塊資料需要寫到磁碟上的,所以這種做法,還是很有必要的。

 

 

2、             標準C++類std::string的Copy-On-Write

 

在我們經常使用的STL標準模板庫中的string類,也是一個具有寫時才拷貝技術的類。C++曾在效能問題上被廣泛地質疑和指責過,為了提高效能,STL中的許多類都採用了Copy-On-Write技術。這種偷懶的行為的確使使用STL的程式有著比較高要效能。

 

這裡,我想從C++類或是設計模式的角度為各位揭開Copy-On-Write技術在string中實現的面紗,以供各位在用C++進行類庫設計時做一點參考。

 

在講述這項技術之前,我想簡單地說明一下string類記憶體配置的概念。通過常,string類中必有一個私人成員,其是一個char*,使用者記錄從堆上分配記憶體的地址,其在構造時分配記憶體,在析構時釋放記憶體。因為是從堆上分配記憶體,所以string類在維護這塊記憶體上是格外小心的,string類在返回這塊記憶體位址時,只返回const char*,也就是唯讀,如果你要寫,你只能通過string提供的方法進行資料的改寫。

 

2.1、         特性

 

由表及裡,由感性到理性,我們先來看一看string類的Copy-On-Write的表面特徵。讓我們寫下下面的一段程式:

 


#include

#include

using namespace std;

 

main()

{

       string str1 = "hello world";

       string str2 = str1;

      

       printf ("Sharing the memory:/n");

       printf ("/tstr1's address: %x/n", str1.c_str() );

       printf ("/tstr2's address: %x/n", str2.c_str() );

      

    str1[1]='q';

       str2[1]='w';

 

       printf ("After Copy-On-Write:/n");

       printf ("/tstr1's address: %x/n", str1.c_str() );

       printf ("/tstr2's address: %x/n", str2.c_str() );

 

       return 0;

}

 

這個程式的意圖就是讓第二個string通過第一個string構造,然後列印出其存放資料的記憶體位址,然後分別修改str1和str2的內容,再查一下其存放記憶體的地址。程式的輸出是這樣的(我在VC6.0和g++ 2.95都得到了同樣的結果):

 


> g++ -o stringTest stringTest.cpp

> ./stringTest

Sharing the memory:

        str1's address: 343be9

        str2's address: 343be9

After Copy-On-Write:

        str1's address: 3407a9

        str2's address: 343be9

 

從結果中我們可以看到,在開始的兩個語句後,str1和str2存放資料的地址是一樣的,而在修改內容後,str1的地址發生了變化,而str2的地址還是原來的。從這個例子,我們可以看到string類的Copy-On-Write技術。

 

 

寫時拷貝可以提高STL庫的效率,但是在我們使用它的時候很容易遇到一些陷阱。

 int main()<br />{<br /> string str1 = "abcd";<br /> string str2 = str1;<br /> char *p1 = const_cast<char*>(str1.c_str());<br /> p1[0] = 'o';<br /> //這裡str1和str2同時被修改了<br /> printf("%s %s/n", str1.c_str(), str2.c_str());<br /> return 0;<br />}

看這個例子,這是看到別人分享的問題,本意肯定不願意str2的值被修改,但是由於STL認為

char *p1 = const_cast<char*>(str1.c_str());   

str1.c_str()這步操作返回的是const char*對象,不會導致寫操作,所以沒有對str1對象進行拷貝處理,而const_cast<char*>在強制轉換後賦值給p1後,後續的寫操作導致了悲劇發生。

 

原因分析完了,如何解決,很簡單,保證寫操作之前,STL已經對該對象拷貝出了記憶體,方法就見仁見智了。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.