天天看點

智能指針之auto_ptr、unique_ptr、shared_ptr

C++11中的四種智能指針

前言

C++ STL 提供了四種智能指針:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。其中auto_ptr 是 C++98 提供的解決方案,C+11 已将其摒棄,并提出了 unique_ptr 作為 auto_ptr 替代方案。雖然 auto_ptr 已被摒棄,但在實際項目中仍可使用,但建議使用較新的 unique_ptr,因為 unique_ptr 比 auto_ptr 更加安全。shared_ptr 和 weak_ptr 則是 C+11 從準标準庫 Boost 中引入的兩種智能指針。此外,Boost 庫還提出了 boost::scoped_ptr、boost::scoped_array、boost::intrusive_ptr 等智能指針,雖然尚未得到 C++ 标準采納,但是在開發實踐中可以使用。

C++11智能指針介紹

智能指針主要用于管理在堆上配置設定的記憶體,它将普通的指針封裝為一個棧對象。當棧對象的生存周期結束後,會在析構函數中釋放掉申請的記憶體,進而防止記憶體洩漏。C++ 11中最常用的智能指針類型為shared_ptr,它采用引用計數的方法,記錄目前記憶體資源被多少個shared_ptr引用。該引用計數的記憶體在堆上配置設定,當新增一個時引用計數加1,當引用過期時計數減一。隻有引用計數為0時,shared_ptr才會自動釋放引用的記憶體資源。對shared_ptr進行初始化時不能将一個普通指針直接指派給智能指針,因為一個是指針,一個是類。可以通過make_shared函數或者通過構造函數傳入普通指針,并可以通過get函數獲得普通指針。

為什麼要使用智能指針

智能指針的作用是管理一個指針,在使用普通指針時存在以下這種情況:申請的空間在函數結束時忘記釋放,造成記憶體洩漏。使用智能指針可以很大程度上的避免這個問題,因為智能指針是一個類,當超出了類的作用域時,類會自動調用析構函數,析構函數會自動釋放資源。是以智能指針的作用原理就是在函數結束時自動釋放記憶體空間,不需要手動釋放記憶體空間。

指針

普通指針存在的問題

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不會報錯      

如果p1和p2是普通指針,那麼兩個指針将指向同一個string對象。那麼在删除同一個對象兩次的時候,會出錯。要避免這種問題,方法有多種:

(1)定義陚值運算符,使之執行深複制。這樣兩個指針将指向不同的對象,其中的一個對象是另一個對象的副本,缺點是浪費空間,是以智能指針都未采用此方案。

(2)建立所有權(ownership)概念。對于特定的對象,隻能有一個智能指針可擁有,這樣隻有擁有對象的智能指針的析構函數會删除該對象。然後讓指派操作轉讓所有權。這就是用于 auto_ptr 和 unique_ptr 的政策,但 unique_ptr 的政策更嚴格。

(3)建立智能更高的指針,跟蹤引用特定對象的智能指針數。這稱為引用計數。例如,指派時,計數将加 1,而指針過期時,計數将減 1,。當減為 0 時才調用 delete。這是 shared_ptr 采用的政策。

auto_ptr

(C++98的方案,C++11已經抛棄)auto_ptr定義在頭檔案​

​<memory>​

​中。采用所有權模式。

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不會報錯      

此時不會報錯,p2剝奪了p1的所有權,但是當程式運作時通路p1将會報錯。是以auto_ptr的缺點是:存在潛在的記憶體崩潰問題!

再來一個例子:

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

int main()
{
    auto_ptr<string> films[5] = {
    auto_ptr<string>(new string("Fowl Balls")),
    auto_ptr<string>(new string("Duck Walks")),
    auto_ptr<string>(new string("Chicken Runs")),
    auto_ptr<string>(new string("Turkey Errors")),
    auto_ptr<string>(new string("Goose Eggs"))
    };
    auto_ptr<string> pwin;
    pwin = films[2]; // films[2] loses ownership. 将所有權從films[2]轉讓給pwin,此時films[2]不再引用該字元串進而變成空指針

    cout << "The nominees for best avian baseballl film are\n";
    for (int i = 0; i < 5; ++i)
    {
        cout << *films[i] << endl;
    }
    cout << "The winner is " << *pwin << endl;
    return 0;
}      

編譯時程式不會出錯,但是運作時程式崩潰。因為films[2] 已經是空指針,​

​*films[2]​

​通路空指針時程式會崩潰。但這裡如果把 auto_ptr 換成 shared_ptr 或 unique_ptr 後,程式就不會崩潰,原因如下:

使用 shared_ptr 時運作正常,因為 shared_ptr 采用引用計數,pwin 和films[2] 都指向同一塊記憶體,在釋放空間時因為事先要判斷引用計數值的大小,是以不會出現多次删除一個對象的錯誤。

使用 unique_ptr 時編譯出錯,與 auto_ptr 一樣unique_ptr 也采用所有權模型,但在使用 unique_ptr 時,程式不會等到運作階段崩潰,而在編譯下述代碼行出現錯誤:

pwin = films[2];  //films[2] loses ownership      
unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt); //編譯出錯,已禁止拷貝
unique_ptr<string> upt1=upt;  //編譯出錯,已禁止拷貝
unique_ptr<string> upt1=std::move(upt);  //控制權限轉移

auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt); //編譯通過
auto_ptr<string> apt1=apt;  //編譯通過      
unique_ptr<string> upt1=std::move(upt); //控制權限轉移
if(upt.get()!=nullptr)               //判空操作更安全
{
  //do something
}      

繼續閱讀