1. 程式人生 > 其它 >string類、智慧指標

string類、智慧指標

1. string 類 p531

1)建構函式

一個比較特殊的建構函式

template <class Iter>
string (Iter begin, Iter end); // 範圍包括 begin 在內,但不包括 end

例如:

char c[] = "All's well that ends well";
...
string s(c+6, c+10);

注意,假設有另外一個 string 物件 s1:

string s1;
...
string s(s1+6, s1+10); // not allowed
string s(&s1[6], &s1[10]; //
allowed

因為 s1 (物件名,不同於陣列名)不會被看作是物件的地址,因此 s1 不是指標,所以 s1 + 6 是沒有意義的;

而 s1[6] 是一個 char 字元,因此 &s1[6] 是一個地址,可以用作該建構函式的一個引數。

2)string 類的輸入 p533

對於 C 風格字串有 3 種輸入方式

char info[100];
cin >> info;
cin.getline(info, 100);// read a line, discard \n
cin.get(info, 100);// read a line, leave \n in queue

對於 string 物件,有兩種輸入方式

string stuff;
cin >> stuff;
getline(cin, stuff);// read a line, discard queue

 string 版本的 getline() 函式從輸入中讀取字元,並將其儲存到目標 string 中,直到發生下列三種情況之一:p534

  • 到達檔案尾;這種情況下,輸入流的 eofbit 將被設定,方法 fail() 和方法 eof() 都返回 true;
  • 遇到分界字元(預設為 \n);這種情況下,把分界字元從輸入流中刪除,但不儲存它;
  • 讀取字元的數量達到最大允許值(string::npos 和可供給分配的記憶體位元組數中較小的一個),這種情況下,將設定輸入流的 failbit 為1,fail() 返回 true;

string 版本的 operator>>() 函式的行為與此類似;只是它不斷讀取,直到遇到空白字元並將其留在輸入佇列中。(cin 不丟棄空白符,p68)

3)string 指定分界符

兩個版本的 getline() 函式都有一個可選引數,用於指定使用哪個字元來確定輸入的邊界:

cin.getline(info, 100, ':'); // 讀取直到遇到 :,並丟棄:
getline(stuff, ':'); // 讀取直到遇到:,並丟棄:

注意,指定分界字元後,換行符 \n 將被視為常規字元。

例子:見 p534

3)使用字串 p535

size(), length(), find()

capacity() // 返回當前分配給字串的記憶體塊的大小

reserve() // 請求記憶體塊長度

2. 智慧指標 p539

智慧指標是行為類似於指標的類物件。

auto_ptr, unique_ptr, shard_ptr 都定義了類似指標的物件,可以將 new 獲得(直接或間接)的地址賦給這種物件。

當智慧指標過期時,其解構函式將使用 delete 來釋放記憶體;在智慧指標過期時,這些記憶體將自動被釋放。

void remodel (std::string & str)
{
    std::string * ps = new std::string(str);
    ...
    if (weird_thing())
        throw exception();
    str = *ps;
    delete ps;
    return;
}

在上述程式碼中,當出現異常時,delete 將不被執行,將導致記憶體洩漏。

1)使用智慧指標

要建立智慧指標物件,必須包含標頭檔案 memory;該檔案包含模板定義,然後使用通常的模板語法來例項化所需型別的指標;

例如,模板 auto_ptr 包含如下建構函式

template<class X>
class auto_ptr {
public:
    explicit auto_ptr(X* p = 0) throw(); //throw() 表示建構函式不會引發異常

請求 X 型別的 auto_ptr 將獲得一個指向 X 型別的 auto_ptr:

auto_ptr<double> pd(new double); // pd an auto_ptr to double(use in place of double * pd)
auto_ptr<string> ps(new string); // ps an auto_ptr to string(use in place of string * ps)

new double 是 new 返回的指標,指向新分配的記憶體塊。它是建構函式 auto_ptr<double> 的引數,即對應於原型中 p 的實參。

其他兩種智慧指標使用同樣的語法:

unique_ptr<double> pdu(new double); // pdu and unique_ptr to double
shared_ptr<string> pss(new string); //pss a shared_ptr to string

因此,可以使用智慧指標來修改 remodel 函式:

#include <memory>
...
void  remodel (std::string & str)
{
    auto_ptr<string> ps (new string(str)); // new string 返回一個 new 申請的空string的地址;new string(str) 返回一個 new 申請的內容為 str 的 string 的地址
    ...
    if (weird_thing())
        throw exception();
    str = *ps;
    //delete ps; NO LONGER NEED
    return;
}

所有智慧指標類都有一個 explicit 建構函式,該建構函式將指標作為引數;因此,不能自動地將指標轉換為智慧指標物件

shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // now allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)

shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); //allowed (explicit conversion)

注意,不能將非堆記憶體中地地址作為智慧指標建構函式地引數

string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // NO!

pvac 過期時,程式將把 delete 運算子用作非堆記憶體,這是錯誤的。

2)智慧指標的區分 p542

auto_ptr 智慧指標在 c++11 中已被摒棄。

auto_ptr<string> ps (new string("I reigned lonely as a cloud."));
auto_ptr<string> vocation;
vocation = ps;

上述程式碼中,如果 ps 和 vocation 是常規指標,則兩個指標指向同一個 string 物件,這樣程式將試圖刪除同一個 string 物件兩次。解決方法如下:

  • 定義賦值運算子,執行深複製。這樣兩個指標將指向不同的物件,其中一個物件是另一個物件的副本;
  • 建立所有權(ownership)概念;對於特定的物件,只有一個智慧指標可以擁有它,只有擁有該物件的智慧指標會刪除該物件;讓賦值操作轉讓所有權;這就是用於 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更嚴格。
  • 跟蹤引用特定物件的智慧指標數,這稱為引用計數(reference counting);僅當最後一個指標過期時,才呼叫 delete;這是 shared_ptr 採用的策略。

auto_ptr, shared_ptr, unique_ptr 的區分

 在 auto_ptr 放棄物件的所有權後,便不能再使用它來訪問物件:

auto_ptr<string> s(new string("Fowl Balls"));
auto_ptr<string> q;
q = s;
cout << *s << endl;// not allowed

上面的程式中 auto_ptr s 將所有權轉讓給 q,因此 s 不再引用該字串。

如果在上述程式中使用 shared_ptr 代替 auto_ptr,則程式將正常執行。在執行 q = s; 語句時,此時 shared_ptr s 和 shared_ptr q 指向同一個物件,引用計數從 1 增加到 2;在程式末尾,後宣告的 q 首先呼叫其解構函式,該解構函式將引用計數降低到1;然後 s 呼叫解構函式時,將引用計數降低到 0,並釋放該字串物件所在的空間。

如果在上述程式中使用 unique_ptr,unique_ptr 與 auto_ptr 一樣,也採用所有權模型;但使用 unique_ptr 時,程式不會等到執行階段崩潰,而在編譯階段因執行下述程式碼而出現錯誤:

q = s;

探討 auto_ptr 和 unique_ptr 之間的區別:

在下面的程式中

auto_ptr<string> p1 (new string("auto")); // #1
auto_ptr<string> p2;                     // #2
p2 = p1;                                 // #3

在語句 #3 中,p2 接管 string 物件的所有權後,p1 的所有權將被剝奪,這可以防止 p1 和 p2 的解構函式試圖刪除同一個物件;但如果程式隨後試圖使用 p1 將會出錯,因為 p1 不再指向有效的資料;

而在

unique_ptr<string> p3 (new string("auto")); // #1
unique_ptr<string> p4;                     // #2
p4 = p3;                                   // #3

編譯器認為 #3 非法,避免了 p3 不再指向有效資料的問題。因此 unique_ptr 比 auto_ptr 更安全;

但是有時候,將一個智慧指標賦給另一個並不會留下危險的懸掛指標

unique_ptr<string> demo(const char * s)
{
    unique_ptr<string> temp(new string(s));
    return temp;
}
...
unique_ptr<string> ps;
ps = demo("Uniquely special")

在上述程式中,demo() 返回一個臨時 unique_ptr,然後該臨時 unique_ptr 將其指向的物件(即 "Uniquely special" 字串)的所有權轉讓給 ps,緊接著該臨時 unique_ptr 被銷燬。

這沒有問題,因為 ps 擁有了 string 物件的所有權;此外,demo() 返回的臨時 unique_ptr 很快被銷燬,沒有機會使用它來訪問無效的資料,沒有理由禁止這種賦值。

編譯器的確允許這種賦值!

總之,如果程式試圖將一個 unique_ptr 賦給另一個時,如果源 unique_ptr 是個臨時右值,編譯器允許這樣做;如果源 unique_ptr 將存在一段時間,編譯器將禁止這樣做

using namespace std;
unique_ptr<string> pu1(new string("Hi ho!"));
unique_ptr<string> pu2;
pu2 = pu1;                                            // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string "Yo!"); // #2 allowed

語句 #1 將留下懸掛的 unique_ptr(pu1),這可能導致危害。語句 #2 不會留下懸掛的 unique_ptr,因為它呼叫 unique_ptr 的建構函式,該建構函式建立的臨時物件在其所有權轉讓給 pu3 後就會被銷燬。

unique_ptr 如何區分安全和不安全的用法?---> 它使用了 C++11 新增的移動建構函式和右值引用。

此外,unique_ptr 還有另一個優點:它有一個可用於陣列的變體。

在之前提到,必須將 new 和 delete 配對使用,將 new [] 和 delete [] 配對使用;

模板 auto_ptr 使用 delete 而不是 lelete [],因此只能與 new 一起使用,而不能與 new [] 一起使用;但 unique_ptr 有使用 new [] 和 delete [] 的版本:

unique_ptr<double []> pda(new double(5)); // will use delete []

注意,使用 new 分配記憶體時,才能使用 auto_ptr 和 shared_ptr,使用 new [] 分配記憶體時,不能使用它們;不使用 new 分配記憶體時,不能使用 auto_ptr 或 shared_ptr;不使用 new 或 new [] 分配記憶體時,不能使用 unique_ptr。