1. 程式人生 > >Effective C++ 條款13 以物件管理資源

Effective C++ 條款13 以物件管理資源

//
//  main.cpp
//  條款13:以物件管理資源
//
//  Created by 於磊 on 2018/7/8.
//  Copyright © 2018 於磊. All rights reserved.
//

#include <iostream>
class Investment {
public:
    ~Investment(){
        std::cout<<" release ! "<<std::endl;
    }
};

Investment *createInvestment() {
    static Investment *investment = nullptr;//條款4
    if (!investment)
        investment = new Investment();
    return investment;
}
int main(int argc, const char *argv[]) {
    Investment *pInv = createInvestment();//不太好的寫法
    std::auto_ptr<Investment>pInv1(createInvestment());//使用auto_ptr注意,在auto_ptr析構時會
    std::auto_ptr<Investment>pInv2(pInv1);             //銷燬所指物,所以多個auto_ptr指向同一物件,
    pInv1 = pInv2;                                     //此物件會被銷燬一次以上,產生未定義行為
    //替代者是shared_ptr
    std::shared_ptr<Investment>pSInv1(createInvestment());
    std::shared_ptr<Investment>pSInv2(pSInv1);
    pSInv1 = pSInv2;
    return 0;
}

1. 在使用堆開闢記憶體後,需要手動delete指標以防記憶體洩露,但有難免會產生不可避免的情況,例如產生異常會使得記憶體資源無法釋放,另外使用者也極有可能忘記釋放“堆記憶體”。因此有必要藉助以物件的解構函式將資源管理起來,以確保資源被正確的釋放,,這種思想被稱為"資源取得時機就是初始化時機"(Resource Acquisition is Initialization; RAII,名字並沒有體現這種思想)

2. 除了手動編寫一個指標類以外,C++標準庫提供了auto_ptr類模板(在<memory>標頭檔案中),使用它需注意:

   a. auto_ptr<T>只能由T*顯示初始化或由auto_ptr<T>物件初始化

   b. auto_ptr<T>只能指向堆記憶體,因為它的解構函式會delete它所指向的物件,因此務必不要用指向堆疊的指標初始化auto_ptr<T>

   c. auto_ptr<T>沒有使用計數的概念,同一塊記憶體只能由一個auto_ptr<T>指向,auto_ptr<T>物件的賦值操作和拷貝構造會使右運算元指向NULL.由於auto_ptr<T>的複製不同於一般意義上的複製,因此它不能作為STL容器的元素.

std::auto_ptr<Investment>pInv1(createInvestment());//使用auto_ptr注意,在auto_ptr析構時會
std::auto_ptr<Investment>pInv2(pInv1);             //銷燬所指物,所以多個auto_ptr指向同一物件,
pInv1 = pInv2;                                     //此物件會被銷燬一次以上,產生未定義行為

3. 從上圖中可以看出,auto_ptr的使用具有一定侷限性和危險性,並且它所管理的記憶體不能同時被多個auto_ptr引用,TR1的"引用計數型智慧指標"(reference-counting smart poiner; RCSP) shared_ptr就是一個很好的替代品."TR1代表'Technical Report 1',那是C++程式庫工作小組對該份文件的稱呼",TR1是C++ 03標準的一個擴充套件,它並不屬於C++ 03標準,只是一份草稿檔案,但它所提出的專案很可能成為官方標準,它敘述了14個新元件,統統放在名稱空間std內,其中包括shared_ptr和weak_ptr,gcc和vs2008SP2以上已經可以支援它了gcc需要include標頭檔案<tr1/memory>,vs需要include標頭檔案<memory>,有的編譯器需要裝boost庫並且include<boost/shared_ptr.hpp>和<boost/weak_ptr.hpp>才可以使用(需加名稱空間限定boost::)

shared_ptr<T>的使用比較簡單,它具有引用計數功能,通過使用use_count()觀測資源的引用計數,使用unique()觀測判斷引用計數是否為1(若為1返回true),使用reset()將指標重設(預設設為空)(詳細成員函式介紹見

http://www.cplusplus.com/reference/memory/shared_ptr/),可以共享物件的所有權,只有最後一個指向該物件的指標被撤銷時,它所指向的堆記憶體才會被釋放,屬於強引用(只要有一個強應用存在,所指物件就不能釋放),因此不能解決迴圈引用的問題,以下就是一個迴圈引用的例子:

#include<iostream>
#include<memory>
using namespace std;
class A;
class B;
class A{
public:
    shared_ptr<B> ptrb;
    ~A(){ cout << "A was destructed!" << endl; }
};
class B{
public:
    shared_ptr<A> ptra;
    ~B(){ cout << "B was destructed!" << endl; }
};
void foo(){
    shared_ptr<A>  s_ptra(new A());
    shared_ptr<B>  s_ptrb(new B());
    s_ptra->ptrb = s_ptrb;
    s_ptrb->ptra = s_ptra;
}

int main(int argc, const char * argv[]) {
    foo();
    return 0;
}

當然無任何執行結果,可見,當foo()被呼叫結束後,儘管ptr_a和ptr_b被銷燬,但a和b形成了迴圈引用,資源無法被釋放。

位於堆記憶體中的a和b的成員ptr_a和ptr_b分別指向a和b導致a和b既不能被釋放,又不能被使用,從而造成迴圈引用.

    要解決迴圈引用,就要使用弱引用——weak_ptr.

    weak_ptr只能通過shared_ptr或weak_ptr來初始化,它不會增加使用計數,也不可以通過它來訪問所引用的物件,它的作用是協助shared_ptr來訪問,通過使用成員函式lock()來獲得一個與它指向相同物件的shared_ptr物件,通過使用use_count()觀測資源的引用計數,使用expired()觀測判斷所指向的記憶體是否被釋放(若被釋放返回true),使用reset()將指標重設(預設設為空)(詳細成員函式介紹見http://www.cplusplus.com/reference/memory/weak_ptr/).要使用弱引用來打破迴圈引用,只要把迴圈引用的其中一方所使用的指標成員改為弱引用即可,即:


#include<iostream>
#include<memory>

using namespace std;
class A;
class B;
class A{
public:
    weak_ptr<B> ptrb;
    ~A(){ cout << "A was destructed!" << endl; }
};
class B{
public:
    shared_ptr<A> ptra;
    ~B(){ cout << "B was destructed!" << endl; }
};
void foo(){
    shared_ptr<A>  s_ptra(new A());
    shared_ptr<B>  s_ptrb(new B());
    s_ptra->ptrb = s_ptrb;
    s_ptrb->ptra = s_ptra;
}

int main(int argc, const char * argv[]) {
    foo();
    return 0;
}

執行結果為:

 4. 注意:

    1). 不管auto_ptr,shared_ptr還是weak_ptr它們只用於指向單個物件而不是陣列,實際上vector和string就可以取代動態分配的陣列,此外,在boost中提供了boost::scoped_array和shared_array用於指向動態分配的陣列.

    2). shared_ptr不要與普通指標互動使用,例如,對於以下程式碼:

 int* p = new int;
    shared_ptr<int> ptr(p);
    cout << ptr.use_count() << endl;
    delete p;
    cout << ptr.use_count();

程式甚至會出錯!

 5. C++標準並沒有針對動態分配陣列"而設計的類似auto_ptr或tr1::shared_ptr的模板(TR1中也沒有),畢竟使用vector和string就可以取代所有動態記憶體分配取得的陣列.幸運的是Boost庫提供的boost::scoped_array和boost::shared_array 提供這種功能.