天天看點

【知識補充】C++理論:類的繼承,多态,模闆,虛函數,智能指針

1. 三種類的繼承方式

        面向對象程式設計中最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得建立和維護一個應用程式變得更容易。這樣做,也達到了重用代碼功能和提高執行效率的效果。

        當建立一個類時,您不需要重新編寫新的資料成員和成員函數,隻需指定建立的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,建立的類稱為派生類。 繼承代表了 is a 關系。例如,哺乳動物是動物,狗是哺乳動物,是以,狗是動物,等等。

// 基類
class Animal {
    // eat() 函數
    // sleep() 函數
};


//派生類
class Dog : public Animal {
    // bark() 函數
};
           

        當一個類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種類型。我們幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。當使用不同類型的繼承時,遵循以下幾個規則:

  • 公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類通路,但是可以通過調用基類的公有和保護成員來通路。
  • 保護繼承(protected): 當一個類派生自保護基類時,基類的公有和保護成員将成為派生類的保護成員。
  • 私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員将成為派生類的私有成員。

1.1 公有繼承public

        基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見;基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的狀态;基類的私有成員不可見,基類的私有成員仍然是私有的,派生類不可通路基類中的私有成員。

        是以,在公有繼承時,派生類的對象可以通路基類中的公有成員,派生類的成員函數可以通路基類中的公有成員和保護成員。簡單來說,派生類能通路基類的public, protected成員,繼承過來權限不變,派生類對象隻能通路基類public成員。

測試代碼如下:

class A
{
private:
    int m_data1;
    void print1() { cout << "private print1" << endl; }
protected:
    int m_data2;
    void print2() { cout << "protected print2" << endl; }
public:
    A(int x = 1, int y = 2, int z = 3) : m_data1(x), m_data2(y), m_data3(z) {}
    int m_data3;
    void print3() { cout << "protected print3" << endl; }
};

class B : public A
{
public:
    void test_public() {
        cout << m_data3 << endl;
        print3();
    }
    void test_protected() {
        cout << m_data2 << endl;
        print2();
    }
    void test_private() {
        // 下面兩行編譯不過,B類内無法通路父類的私有成員
        // cout << m_data1 << endl;  
        // print1();
    }
};


int main(int argc, char const* argv[])
{
    B b;
    b.test_public();
    b.test_protected();
    b.test_private();
    cout << b.m_data3 << endl;
    // cout << b.m_data2 << endl;  // 編譯不過,子類對象無法通路父類protected的成員
    // cout << b.m_data1 << endl;  // 編譯不過,子類對象無法通路父類private的成員
    return 0;
}
           

1.2 私有繼承private 

        基類成員對其對象的可見性與一般類及其對象的可見性相同,公有成員可見,其他成員不可見;基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員是可見的,基類的公有成員和保護成員都作為派生類的私有成員,并且不能被這個派生類的子類所通路;基類的私有成員是不可見的,派生類不可通路基類中的私有成員。

        基類成員對派生類對象的可見性對派生類對象來說,基類的所有成員都是不可見的。是以,在私有繼承時,基類的成員隻能由直接派生類通路,而無法再往下繼承。簡單來說派生類可以通路基類的public, protected成員,繼承過來之後變成自己私有的。派生類的對象啥都不能通路。

class A
{
private:
    int m_data1;
    void print1() { cout << "private print1" << endl; }
protected:
    int m_data2;
    void print2() { cout << "protected print2" << endl; }
public:
    A(int x = 1, int y = 2, int z = 3) : m_data1(x), m_data2(y), m_data3(z) {}
    int m_data3;
    void print3() { cout << "protected print3" << endl; }
};

class B : private A
{
public:
    void test_public() {
        cout << m_data3 << endl;
        print3();
    }
    void test_protected() {
        cout << m_data2 << endl;
        print2();
    }
    void test_private() {
        // 下面兩行編譯不過,B類内無法通路父類的私有成員
        // cout << m_data1 << endl;  
        // print1();
    }
};


int main(int argc, char const* argv[])
{
    B b;
    b.test_public();
    b.test_protected();
    b.test_private();
    // cout << b.m_data3 << endl;  // // 編譯不過,子類對象無法通路父類public的成員
    // cout << b.m_data2 << endl;  // 編譯不過,子類對象無法通路父類protected的成員
    // cout << b.m_data1 << endl;  // 編譯不過,子類對象無法通路父類private的成員
    return 0;
}
           

1.3 保護繼承protected

        保護繼承與私有是一樣的。

public 的方式繼承到派生類,這些成員的權限和在基類裡的權限保持一緻;

protected方式繼承到派生類,成員的權限都變為protected;

private 方式繼承到派生類,成員的權限都變為private;

        對于三種方式派生類的對象來說: 隻有public的方式繼承後,派生來的對象隻能通路基類的public成員,protected和private方式繼承,派生類的對象都不可以通路父類的成員。

2. 多态

2.1  多态的概念與分類

        多态(Polymorphisn)是面向對象程式設計(OOP)的一個重要特征。多态按字面的意思就是多種形态。當類之間存在層次結構,并且類之間是通過繼承關聯時,就會用到多态。在面向對象語言中,一個接口,多種實作即為多态。C++ 中的多态性具體展現在編譯和運作兩個階段。編譯時多态是靜态多态,在編譯時就可以确定使用的接口。運作時多态是動态多态,具體引用的接口在運作時才能确定。C++ 多态意味着調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。

【知識補充】C++理論:類的繼承,多态,模闆,虛函數,智能指針

        靜态多态和動态多态的差別其實隻是在什麼時候将函數實作和函數調用關聯起來,是在編譯時期還是運作時期,即函數位址是早綁定還是晚綁定的。靜态多态是指在編譯期間就可以确定函數的調用位址,并生産代碼,這就是靜态的,也就是說位址是早綁定。靜态多态往往也被叫做靜态聯編。 動态多态則是指函數調用的位址不能在編譯器期間确定,需要在運作時确定,屬于晚綁定,動态多态往往也被叫做動态聯編。

2.2 多态的作用

        為何要使用多态呢?封裝可以使得代碼子產品化,繼承可以擴充已存在的代碼,他們的目的都是為了代碼重用。而多态的目的則是為了接口重用。靜态多态,将同一個接口進行不同的實作,根據傳入不同的參數(個數或類型不同)調用不同的實作。動态多态,則不論傳遞過來的哪個類的對象,函數都能夠通過同一個接口調用到各自對象實作的方法。

2.3 靜态多态

        靜态多态往往通過函數重載和模版(泛型程式設計)來實作,具體可見下面代碼:

#include <iostream>
using namespace std;

//兩個函數構成重載
int add(int a, int b){
    cout<<"in add_int_int()"<<endl;
    return a + b;
}
double add(double a, double b){
    cout<<"in add_double_doube()"<<endl;
    return a + b;
}

//函數模闆(泛型程式設計)
template <typename T> T add(T a, T b){
    cout << "in func tempalte" << endl;
    return a + b;
}

int main(){
    cout<<add(1,1)<<endl;					//調用int add(int a, int b)
    cout<<add(1.1,1.1)<<endl;		   	 	//調用double add(double a, double b)
    cout<<add<char>('A',' ')<<endl;		//調用模闆函數,輸出小寫字母a
}
           

2.4 動态多态

        動态多态最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而調用不同的方法。如果沒有使用虛函數,即沒有利用 C++ 多态性,則利用基類指針調用相應函數的時候,将總被限制在基類函數本身,而無法調用到子類中被重寫的函數。因為沒有多态性,函數調用的位址将是一定的,而固定的位址将始終調用同一個函數,這就無法達到“一個接口,多種實作”的目的了。

#include <iostream> 
using namespace std;

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()//虛函數,可以通過子類重寫
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()//重寫父類的虛函數
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};

class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};

// 程式的主函數
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 存儲矩形的位址
   shape = &rec;
   // 調用矩形的求面積函數 area
   shape->area();
 
   // 存儲三角形的位址
   shape = &tri;
   // 調用三角形的求面積函數 area
   shape->area();
   
   return 0;
}
           

輸出結果:

Rectangle class area
Triangle class area
           

        通過上面的例子可以看出,在使用基類指針或引用指向子類對象時,調用的函數是子類中重寫的函數,這樣就實作了運作時函數位址的動态綁定,即動态聯編。動态多态是通過“繼承+虛函數”來實作的,隻有在程式運作期間(非編譯期)才能判斷所引用對象的實際類型,根據其實際類型調用相應的方法。具體格式就是使用 virtual 關鍵字修飾類的成員函數時,指明該函數為虛函數,并且派生類需要重新實作該成員函數,編譯器将實作動态綁定。有了多态,您可以有多個不同的類,都帶有同一個名稱但具有不同實作的函數,函數的參數甚至可以是相同的。

需要注意:

  1. 隻有類的成員函數才能聲明為虛函數,虛函數僅适用于有繼承關系的類對象。普通函數不能聲明為虛函數。
  2. 靜态成員函數不能是虛函數,因為靜态成員函數不受限于某個對象。
  3. 内聯函數(inline)不能是虛函數,因為内聯函數不能在運作中動态确定位置。
  4. 構造函數不能是虛函數。
  5. 析構函數可以是虛函數,而且建議聲明為虛函數。

2.5 重寫 vs 重載

        重寫可以有兩種,直接重寫成員函數和重寫虛函數,隻有重寫了虛函數的才能算作是展現了C++多态性。而重載則是允許有多個同名的函數,而這些函數的參數清單不同,允許參數個數不同,參數類型不同,或者兩者都不同。編譯器會根據這些函數的不同清單,将同名的函數的名稱做修飾,進而生成一些不同名稱的預處理函數,來實作同名函數調用時的重載問題。但這并沒有展現多态性。

2.6 隐藏

        除了重載與覆寫(重寫),C++還有隐藏。隐藏是指派生類的函數屏蔽了與其同名的基類函數。隐藏規則如下:

  1. 如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual 關鍵字,基類的函數将被隐藏(注意别與重載混淆,重載是在同一個類中發生)。
  2. 如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆,覆寫有virtual關鍵字)。

2.7 模闆

         模闆是C++支援參數化多态的工具,使用模闆可以使使用者為類或者函數聲明一種一般模式,使得類中的某些資料成員或者成員函數的參數、傳回值取得任意類型。

//函數模闆的格式:
template <class 形參名,class 形參名,......> 傳回類型 函數名(參數清單)
{
    函數體
}
           

其中template和class是關見字,class可以用typename 關見字代替,在這裡typename 和class沒差別,<>括号中的參數叫模闆形參,模闆形參和函數形參很相像,模闆形參不能為空。一但聲明了模闆函數就可以用模闆函數的形參名聲明類中的成員變量和成員函數,即可以在該函數中使用内置類型的地方都可以使用模闆形參名。模闆形參需要調用該模闆函數時提供的模闆實參來初始化模闆形參,一旦編譯器确定了實際的模闆實參類型就稱他執行個體化了函數模闆的一個執行個體。比如swap的模闆函數形式為:

template <class T> void swap(T& a, T& b){},
           

        當調用這樣的模闆函數時類型T就會被被調用時的類型所代替,比如swap(a,b)其中a和b是int 型,這時模闆函數swap中的形參T就會被int 所代替,模闆函數就變為swap(int &a, int &b)。而當swap(c,d)其中c和d是double類型時,模闆函數會被替換為swap(double &a, double &b),這樣就實作了函數的實作與類型無關的代碼。

類模闆的格式為:

template<class  形參名,class 形參名,…>   class 類名
{ ... };
           

        類模闆和函數模闆都是以template開始後接模闆形參清單組成,模闆形參不能為空,一但聲明了類模闆就可以用類模闆的形參名聲明類中的成員變量和成員函數,即可以在類中使用内置類型的地方都可以使用模闆形參名來聲明。比如

template<class T> class A{public: T a; T b; T hy(T c, T &d);};
           

        在類A中聲明了兩個類型為T的成員變量a和b,還聲明了一個傳回類型為T帶兩個參數類型為T的函數hy。

3. 虛函數與純虛函數

        虛函數 是在基類中使用關鍵字 virtual 聲明的函數。在派生類中重新定義基類中定義的虛函數時,會告訴編譯器不要靜态連結到該函數。我們想要的是在程式中任意點可以根據所調用的對象類型來選擇調用的函數,這種操作被稱為動态連結,或後期綁定。

        您可能想要在基類中定義虛函數,以便在派生類中重新定義該函數更好地适用于對象,但是您在基類中又不能對虛函數給出有意義的實作,這個時候就會用到純虛函數。

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};
           

= 0 告訴編譯器,函數沒有主體,上面的虛函數是純虛函數。

4. 智能指針

        由于 C++ 語言沒有自動記憶體回收機制,程式員每次

new

出來的記憶體都要手動

delete

。程式員忘記

delete

,流程太複雜,最終導緻沒有

delete

,異常導緻程式過早退出,沒有執行

delete

的情況并不罕見。

        對于編譯器來說,智能指針實際上是一個棧對象,并非指針類型,在棧對象生命期即将結束時,智能指針通過析構函數釋放有它管理的堆記憶體。所有智能指針都重載了

operator->

操作符,直接傳回對象的引用,用以操作對象。通路智能指針原來的方法則使用

.

操作符。

        通路智能指針包含的裸指針則可以用 

get()

 函數。由于智能指針是一個對象,是以

if (my_smart_object)

永遠為真,要判斷智能指針的裸指針是否為空,需要這樣判斷:

if (my_smart_object.get())

        智能指針包含了 

reset()

 方法,如果不傳遞參數(或者傳遞 NULL),則智能指針會釋放目前管理的記憶體。如果傳遞一個對象,則智能指針會釋放目前對象,來管理新傳入的對象。

c++主要的四個智能指針: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中後三個是c++11支援,并且第一個已經被c++11棄用。

4.1 auto_ptr

std::auto_ptr

 屬于 STL,當然在 

namespace std

 中,包含頭檔案

#include<memory>

便可以使用。 

std::auto_ptr

 能夠友善的管理單個堆記憶體對象。

我們從代碼開始分析:

{
  std::auto_ptr<A> a_ptr(new A());  // 建立對象

  if(a_ptr.get())                   // 判斷智能指針是否為空
  {
      a_ptr->show_message();        // 使用 operator-> 調用智能指針對象中的函數
      a_ptr.get()->show_message();  // 使用 get() 傳回裸指針
  }
}                                   // a_ptr 生命周期結束,析構堆對象 A
           

上述為正常使用 std::auto_ptr 的代碼,一切似乎都良好,無論如何不用我們顯式使用該死的delete 了。

問題 1

void TestAutoPtr2() {
  std::auto_ptr<A> my_memory(new A(1));

  if (my_memory.get()) {
    std::auto_ptr<A> my_memory2;      // 建立一個新的 my_memory2 對象
    my_memory2 = my_memory;           // 複制舊的 my_memory 給 my_memory2
    my_memory2->show_message();       // 輸出資訊,複制成功
    my_memory->show_message();        // 崩潰
  }
}
           

最終如上代碼導緻崩潰,如上代碼時絕對符合 C++ 程式設計思想的,居然崩潰了,跟進

std::auto_ptr

的源碼後,我們看到,罪魁禍首是

my_memory2 = my_memory

,這行代碼,

my_memory2

 完全奪取了 

my_memory

 的記憶體管理所有權,導緻 

my_memory

 懸空,最後使用時導緻崩潰。

是以,使用 std::auto_ptr 時,絕對不能使用“operator=”操作符。作為一個庫,不允許使用者使用,确沒有明确拒絕,多少會覺得有點出乎預料。

問題 2

void TestAutoPtr3() {
  std::auto_ptr<A> my_memory(new A(1));

  if (my_memory.get()) {
    my_memory.release();
  }
}                // 沒有析構
           

看到什麼異常了嗎?我們建立出來的對象沒有被析構,導緻記憶體洩露。當我們不想讓

my_memory

繼續生存下去,我們調用 

release()

 函數釋放記憶體,結果卻導緻記憶體洩露(在記憶體受限系統中,如果

my_memory

占用太多記憶體,我們會考慮在使用完成後,立刻歸還,而不是等到

my_memory

結束生命期後才歸還)。

void TestAutoPtr3() {
  std::auto_ptr<A> my_memory(new A(1));
  if (my_memory.get()) {
    A* temp_memory = my_memory.release();
    delete temp_memory;
  }
}
           

4.2 unique_ptr

unique_ptr

 由 

C++11

 引入,旨在替代不安全的 

auto_ptr

unique_ptr

 是一種定義在頭檔案

<memory>

中的智能指針。

它持有對對象的獨有權——兩個

unique_ptr

不能指向一個對象,即 

unique_ptr

 不共享它所管理的對象。

它無法複制到其他

unique_ptr

,無法通過值傳遞到函數,也無法用于需要副本的任何标準模闆庫 (STL)算法。隻能移動 

unique_ptr

,即對資源管理權限可以實作轉移。這意味着,記憶體資源所有權可以轉移到另一個

unique_ptr

,并且原始 

unique_ptr

 不再擁有此資源。

實際使用中,建議将對象限制為由一個所有者所有,因為多個所有權會使程式邏輯變得複雜。是以,當需要智能指針用于存 C++ 對象時,可使用 

unique_ptr

,構造 

unique_ptr

 時,可使用 

make_unique

 Helper 函數。

下圖示範了兩個 unique_ptr 執行個體之間的所有權轉換。

【知識補充】C++理論:類的繼承,多态,模闆,虛函數,智能指針

unique_ptr

 與原始指針一樣有效,并可用于 STL 容器。将 

unique_ptr

 執行個體添加到 STL 容器運作效率很高,因為通過 

unique_ptr

 的移動構造函數,不再需要進行複制操作。

unique_ptr

 指針與其所指對象的關系:在智能指針生命周期内,可以改變智能指針所指對象,如建立智能指針時通過構造函數指定、通過 

reset

 方法重新指定、通過 

release

 方法釋放所有權、通過移動語義轉移所有權,

unique_ptr

 還可能沒有對象,這種情況被稱為 

empty

//智能指針的建立  
unique_ptr<int> u_i; 	//建立空智能指針
u_i.reset(new int(3)); 	//綁定動态對象  
unique_ptr<int> u_i2(new int(4));//建立時指定動态對象
unique_ptr<T,D> u(d);	//建立空 unique_ptr,執行類型為 T 的對象,用類型為 D 的對象 d 來替代預設的删除器 delete

//所有權的變化  
int *p_i = u_i2.release();	//釋放所有權  
unique_ptr<string> u_s(new string("abc"));  
unique_ptr<string> u_s2 = std::move(u_s); //所有權轉移(通過移動語義),u_s所有權轉移後,變成“空指針” 
u_s2.reset(u_s.release());	//所有權轉移
u_s2=nullptr;//顯式銷毀所指對象,同時智能指針變為空指針。與u_s2.reset()等價
           

4.3 shared_ptr

shared_ptr

是一個标準的共享所有權的智能指針,允許多個指針指向同一個對象,定義在

memory

檔案中,命名空間為

std

。 

shared_ptr

最初實作于Boost庫中,後由 

C++11

 引入到 

C++ STL

shared_ptr

 利用引用計數的方式實作了對所管理的對象的所有權的分享,即允許多個 

shared_ptr

 共同管理同一個對象。 像 

shared_ptr

 這種智能指針,《Effective C++》稱之為“引用計數型智能指針”(reference-counting smart pointer,RCSP)。

shared_ptr

 是為了解決 

auto_ptr

 在對象所有權上的局限性(

auto_ptr

 是獨占的),在使用引用計數的機制上提供了可以共享所有權的智能指針,當然這需要額外的開銷:

  1. shared_ptr

     對象除了包括一個所擁有對象的指針外,還必須包括一個引用計數代理對象的指針;
  2. 時間上的開銷主要在初始化和拷貝操作上, 

    *

     和 

    ->

     操作符重載的開銷跟 

    auto_ptr

     是一樣;
  3. 開銷并不是我們不使用 

    shared_ptr

     的理由,永遠不要進行不成熟的優化,直到性能分析器告訴你這一點。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
    shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
    shared_ptr<A> sp2(sp1);       //A(2)同時交由sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同時交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get傳回托管的指針,p 指向 A(2)
    cout << p->i << endl;  //輸出 2
    sp1.reset(new A(3));    // reset導緻托管新的指針, 此時sp1托管A(3)
    sp2.reset(new A(4));    // sp2托管A(4)
    cout << sp1->i << endl; //輸出 3
    sp3.reset(new A(5));    // sp3托管A(5),A(2)無人托管,被delete
    cout << "end" << endl;
    return 0;
}
           

4.4 weak_ptr

weak_ptr

 被設計為與 

shared_ptr

 共同工作,可以從一個 

shared_ptr

 或者另一個 

weak_ptr

 對象構造而來。

weak_ptr

 是為了配合 

shared_ptr

 而引入的一種智能指針,它更像是 

shared_ptr

 的一個助手而不是智能指針,因為它不具有普通指針的行為,沒有重載 

operator*

 和 

operator->

 ,是以取名為 weak,表明其是功能較弱的智能指針。

它的最大作用在于協助 

shared_ptr

 工作,可獲得資源的觀測權,像旁觀者那樣觀測資源的使用情況。觀察者意味着 

weak_ptr

 隻對 

shared_ptr

 進行引用,而不改變其引用計數,當被觀察的 

shared_ptr

 失效後,相應的 

weak_ptr

 也相應失效。

使用 

weak_ptr

 的成員函數 

use_count()

 可以觀測資源的引用計數,另一個成員函數 

expired()

 的功能等價于 

use_count()==0

,但更快,表示被觀測的資源(也就是

shared_ptr

管理的資源)已經不複存在。

weak_ptr

可以使用一個非常重要的成員函數

lock()

從被觀測的 

shared_ptr

 獲得一個可用的 

shared_ptr

 管理的對象, 進而操作資源。但當 

expired()==true

 的時候,

lock()

 函數将傳回一個存儲空指針的 

shared_ptr

weak_ptr的基本用法總結如下:

weak_ptr<T> w;	 	     // 建立空 weak_ptr,可以指向類型為 T 的對象。
weak_ptr<T> w(sp);	   // 與 shared_ptr 指向相同的對象,shared_ptr 引用計數不變。T必須能轉換為 sp 指向的類型。
w=p;				           // p 可以是 shared_ptr 或 weak_ptr,指派後 w 與 p 共享對象。
w.reset();			       // 将 w 置空。
w.use_count();		     // 傳回與 w 共享對象的 shared_ptr 的數量。
w.expired();		       // 若 w.use_count() 為 0,傳回 true,否則傳回 false。
w.lock();			         // 如果 expired() 為 true,傳回一個空 shared_ptr,否則傳回非空 shared_ptr
           

下面是一個簡單的使用示例:

#include < assert.h>

#include <iostream>
#include <memory>
#include <string>
using namespace std;

int main()
{
	shared_ptr<int> sp(new int(10));
	assert(sp.use_count() == 1);
	weak_ptr<int> wp(sp); 	//從shared_ptr建立weak_ptr
	assert(wp.use_count() == 1);
	if (!wp.expired())		//判斷weak_ptr觀察的對象是否失效
	{
		shared_ptr<int> sp2 = wp.lock();//獲得一個shared_ptr
		*sp2 = 100;
		assert(wp.use_count() == 2);
	}
	assert(wp.use_count() == 1);
	cout << "int:" << *sp << endl;
    return 0;
}
           

4.5 如何選擇智能指針

文簡單地介紹了 C++ 标準模闆庫 STL 中四種智能指針,當然,除了STL 中的智能指針,C++ 準标準庫 Boost 中的智能指針,比如boost::scoped_ptr、boost::shared_array、boost:: intrusive_ptr 也可以在實際程式設計實踐中拿來使用.

  1. 如果程式要使用多個指向同一個對象的指針,應選擇shared_ptr;
  2. 兩個對象都包含指向第三個對象的指針 STL容器包含指針。很多STL算法都支援複制和指派操作,這些操作可用于shared_ptr,但不能用于unique_ptr(編譯器發出warning)和auto_ptr(行為不确定)。如果你的編譯器沒有提供shared_ptr,可使用Boost庫提供的shared_ptr;
  3. 如果程式不需要多個指向同一個對象的指針,則可使用

    unique_ptr

    。如果函數使用

    new

    配置設定記憶體,并返還指向該記憶體的指針,将其傳回類型聲明為

    unique_ptr

    是不錯的選擇。這樣,所有權轉讓給接受傳回值的 

    unique_ptr

    ,而該智能指針将負責調用 delete。可将 

    unique_ptr

     存儲到 STL 容器中,隻要不調用将一個 

    unique_ptr

     複制或指派給另一個的算法(如 sort())。例如,可在程式中使用類似于下面的代碼段。
unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}

void show(unique_ptr<int>& p1)
{
    cout << *p1 << ' ';
}

int main()
{
    ...
    vector<unique_ptr<int> > vp(size);
    for(int i = 0; i < vp.size(); i++)
		vp[i] = make_int(rand() % 1000);           // copy temporary unique_ptr
    vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);      // use for_each()
    ...
}
           

其中 

push_back

 調用沒有問題,因為它傳回一個臨時 

unique_ptr

,該 

unique_ptr

 被賦給 vp 中的一個 

unique_ptr

另外,如果按值而不是按引用給 

show()

 傳遞對象,

for_each()

 将非法,因為這将導緻使用一個來自 vp 的非臨時 

unique_ptr

 初始化 pi,而這是不允許的。前面說過,編譯器将發現錯誤使用 

unique_ptr

 的企圖。

在 

unique_ptr

 為右值時,可将其賦給 

shared_ptr

,這與将一個 

unique_ptr

 賦給另一個 

unique_ptr

 需要滿足的條件相同,即 

unique_ptr

 必須是一個臨時對象。與前面一樣,在下面的代碼中,

make_int()

 的傳回類型為 

unique_ptr<int>

unique_ptr<int> pup(make_int(rand() % 1000));   	// ok
shared_ptr<int> spp(pup);                    	    // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000));    	// ok
           

模闆 

shared_ptr

 包含一個顯式構造函數,可用于将右值 

unique_ptr

 轉換為 

shared_ptr

shared_ptr

 将接管原來歸 

unique_ptr

 所有的對象。

在滿足 

unique_ptr

 要求的條件時,也可使用 

auto_ptr

,但 

unique_ptr

 是更好的選擇。如果你的編譯器沒有 

unique_ptr

,可考慮使用 Boost 庫提供的 

scoped_ptr

,它與 

unique_ptr

 類似。

參考文章:

C++ STL 四種智能指針_Dablelv的部落格專欄-CSDN部落格

C++ 智能指針詳解 - 大氣象 - 部落格園

C11 智能指針

C++ 多态的兩種形式

菜鳥教程

5分鐘掌握多态的幾種方式

繼續閱讀