1. 程式人生 > >標準C++類std::string的記憶體共享和Copy-On-Write技術

標準C++類std::string的記憶體共享和Copy-On-Write技術

1概念

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

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

20%的程式運行了80%的時間。

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

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

Unix就是這樣一個系統,如果非正常退出,那麼資料就會丟失,檔案就會損壞。

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

2標準C++std::stringCopy-On-Write

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

簡單地說明一下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構造,然後打印出其存放資料的記憶體地址,然後分別修改str1str2的內容,再查一下其存放記憶體的地址。程式的輸出是這樣的(VC6.0g++ 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

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

2.2深入

我們應該知道在string類中,要實現寫時才拷貝,需要解決兩個問題,一個是記憶體共享,一個是Copy-On-Wirte,這兩個主題會讓我們產生許多疑問:

1 Copy-On-Write的原理是什麼?

2 string類在什麼情況下才共享記憶體的?

3 string類在什麼情況下觸發寫時才拷貝(Copy-On-Write?

4 Copy-On-Write時,發生了什麼?

5 Copy-On-Write的具體實現是怎麼樣的?

2.3Copy-On-Write的原理是什麼?

有一定經驗的程式設計師一定知道,Copy-On-Write一定使用了引用計數,是的,必然有一個變數類似於RefCnt。當第一個類構造時,string的建構函式會根據傳入的引數從堆上分配記憶體,當有其它類需要這塊記憶體時,這個計數為自動累加,當有類析構時,這個計數會減一,直到最後一個類析構時,此時的RefCnt1或是0,此時,程式才會真正的Free這塊從堆上分配的記憶體。

引用計數就是string類中寫時才拷貝的原理!

不過,問題又來了,這個RefCnt該存在在哪裡呢?如果存放在string類中,那麼每個string的例項都有各自的一套,根本不能共有一個RefCnt,如果是宣告成全域性變數,或是靜態成員,那就是所有的string類共享一個了,這也不行,我們需要的是一個民主和集中的一個解決方法。

2.3.1string類在什麼情況下才共享記憶體的?

這個問題的答案應該是明顯的,根據常理和邏輯,如果一個類要用另一個類的資料,那就可以共享被使用類的記憶體了。這是很合理的,如果你不用我的,那就不用共享,只有你使用我的,才發生共享。

使用別的類的資料時,無非有兩種情況,1)以別的類構造自己,2)以別的類賦值。第一種情況時會觸發拷貝建構函式,第二種情況會觸發賦值操作符。這兩種情況我們都可以在類中實現其對應的方法。對於第一種情況,只需要在string類的拷貝建構函式中做點處理,讓其引用計數累加;同樣,對於第二種情況,只需要過載string類的賦值操作符,同樣在其中加上一點處理。

NOTE:

1)構造和賦值的差別

對於前面那個例程中的這兩句:

       string str1 = "hello world";

       string str2 = str1;

不要以為有“=”就是賦值操作,其實,這兩條語句等價於:

       string str1 ("hello world");   //呼叫的是建構函式

       string str2 (str1);            //呼叫的是拷貝建構函式

如果str2是下面的這樣情況:

string str2;      //呼叫引數預設為空串的建構函式:string str2(“”);

str2 = str1;     //呼叫str2的賦值操作:str2.operator=(str1);

2) 另一種情況

相關推薦

標準Cstd::string記憶體共享Copy-On-Write技術

1、概念 Scott Meyers在《More Effective C++》中舉了個例子,不知你是否還記得?在你還在上學的時候,你的父母要你不要看電視,而去複習功課,於是你把自己關在房間裡,做出一副正在複習功課的樣子,其實你在幹著別的諸如給班上的某位女生寫情書之類的

【轉】標準Cstd::string的內存共享Copy-On-Write技術

信息 在哪裏 主程序 分析 ash 3.4 alloc 是否 今天 1、 概念 Scott Meyers在《More Effective C++》中舉了個例子,不知你是否還記得?在你還在上學的時候,你的父母要你不要看電視,而去復習功課,於是你

標準CstringCopy-On-Write技術(一)

寫時才拷貝(Copy-On-Write)技術,就是程式設計界“懶惰行為”——拖延戰術的產物。舉個例子,比如我們有個程式要寫檔案,不斷地根據網路傳來的資料寫,如果每一次fwrite或是fprintf都要進行一個磁碟的I/O操作的話,都簡直就是效能上巨大的損失,因此通常的做法是,每次寫檔案操作都寫在特定大小的一塊

標準CstringCopy-On-Write技術(二)

不過,問題又來了,這個RefCnt該存在在哪裡呢?如果存放在string類中,那麼每個string的例項都有各自的一套,根本不能共有一個RefCnt,如果是宣告成全域性變數,或是靜態成員,那就是所有的string類共享一個了,這也不行,我們需要的是一個“民主和集中”的一個解決方法。這是如何做到的呢?呵呵,人生

標準c++實現CStringTCHAR

程式要從windows下移植到linux下,CFlie類還好說,用到的地方比較集中,統一改成fstream就好,最坑的就數這個CString類了,分佈在各個檔案中,只好實現一個CString類。程式碼主要借鑑了網上的程式碼不是我重頭寫的,借鑑了好幾個,就不列出出處了,如果你發現是你的程式

C#使用MemoryStream讀寫記憶體

MemoryStream和BufferedStream都派生自基類Stream,因此它們有很多共同的屬性和方法,但是每一個類都有自己獨特的用法。這兩個類都是實現對記憶體進行資料讀寫的功能,而不是對永續性儲存器進行讀寫。   讀寫記憶體-MemoryStream類 MemoryStream類用於

C++中的所佔記憶體空間總結(其中有一段關於成員函式處於程式碼段的解釋) 2011-12-9 16:16

#include<iostream.h> class a {}; class b{}; class c:public a{ virtual void fun()=0; }; class d:public b,public c{}; int main() { cout<&

標準C時間與日期函式、記憶體函式

標準C時間與日期函式 asctime() 時間文字格式 clock() 返回自程式開始執行所經過的時間 ctime() 返回特定格式時間

完成一程式演示字元陣列、C風格字串、std::stringMFC中的Cstring型別之間的相互轉換

《C++標準函式庫》中說的有三個函式可以將字串的內容轉換為字元陣列和C—string1.data(),返回沒有”\0“的字串陣列2,c_str(),返回有”\0“的字串陣列3,copy().............................................................

C++中實現從std::string型別到bool型的轉換

利用輸入字串流:std::istringstream   ? 1 2 3 boolb; std::string s = "true"; std::istringstream(s) >> std::boolalpha >>

標準C++複數運算詳解及使用例程

    在C++中複數運算可以通過兩種方式來實現:     1)標準C++複數運算庫:complex<typedef> ObjectName(realPart, imagePart);     2)自定義複數運算類:包括複數的實部、虛部、四則運算、模運算、共軛等

[C/C++]_[初級]_[使用C字串(或者std::string)處理函式獲取檔案所在目錄

//1.使用C字串處理函式獲取檔案所在目錄。 //2.使用std::string處理函式獲取檔案所在目錄。 //練習 //例如:檔案路徑:E:\software\practices\aa.png //    目錄:輸出  E:\software\practices\ #in

C++ string 部分成員函式實現(實現COW copy-on-write

雖然標題中說實現了COW,但是事實上是很浪費的,並且命名也很不標準,程式碼也非常小學生,畢竟初學(給自己找藉口.jpg),以後應該還會把這篇找出來認真修改一下的。 Mystring.h: #pragma once #ifndef _MYSTRING_H_ #define

C++之智慧指標std::shared_ptr簡單使用理解

1  智慧指標std::shared_ptr相關知識和如何使用 我們這裡先說下智慧指標std::shared_ptr,因為我看到我我們專案c++程式碼裡面用得很多,我不是不會,所以記錄學習下 先讓ubuntu終端支援c++11,如果自己的電腦還沒配置號,可以先看下我的這篇部落格

string的簡單實現(寫時拷貝Copy-on-write

前言:上一篇文章實現了string的深拷貝寫法;那我們能不能用淺拷貝寫string類呢?當然可以; 一、 (1) 當我們需要對拷貝之後的物件進行修改時,採用深拷貝的方式; 如果不需要修改,只是輸出字串的內容時,或者是當偶爾修改的的時候,我們再採用深拷貝的方

C++虛基的作用、用法意義

教科書上面對C++虛基類的描述玄而又玄,名曰“共享繼承”,名曰“各派生類的物件共享基類的的一個拷貝”,其實說白了就是解決多重多級繼承造成的二義性問題。例如有基類B,從B派生出C和D,然後類F又同時繼承了C和D,現在類F的一個物件裡面包含了兩個基類B的物件,如果F訪問自己的從

C++ primer plus 第9章 記憶體模型名稱空間

C++11中,關鍵字auto用於自動型別推斷,但在C語言和以前的C++版本中,auto的含義截然不同,它用於顯式地指出變數Wie自動儲存。 1.自動變數和棧 程式必須在執行時對自動變數進行管理,常用方法:留出一段記憶體,並將其視為棧,以管理變數的增減。程式使用兩個指標來跟蹤棧,一個指標指向棧

std::string的reserve()resize()函式的區別

參考:http://blog.csdn.net/freecloud_insky/article/details/47058597 分析以下程式碼,可見 #include <std

Java基礎庫——String、StringBufferStringBuilder

字串就是一連串的字元序列,Java提供了String和StringBuffer兩個類來封裝字串,並提供了一系列方法來操作字串物件。 String類是不可變類,即一旦一個String物件被建立以後,包含在這個物件中的字元序列是不可改變的,直到這個物件被銷燬。 StringBu

C++中關於重複定義的分析解決方法

在C++中將類以及類中的成員函式的宣告放在.h的標頭檔案中,而將類中成員函式的定義(即實現程式碼)放在.cpp的原始檔中,這樣我們的程式設計起來更加的模組化,但是,這樣的設計也會帶來一些問題,我們分析以下的程式碼,從中找的問題,並給出問題的解決方法。首先我們在VC下新建一個