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

提示你發現潛在的內存錯誤。這就是爲何要摒棄 auto_ptr 的原因,一句話總結就是:避免因潛在的內存問題導致程序崩潰。

從上面可見,unique_ptr 比 auto_ptr 更加安全,因爲 auto_ptr 有拷貝語義,拷貝後原對象變得無效,再次訪問原對象時會導致程序崩潰;unique_ptr 則禁止了拷貝語義,但提供了移動語義,即可以使用std::move() 進行控制權限的轉移,如下代碼所示:

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;	//編譯通過

這裏要注意,在使用std::move將unique_ptr的控制權限轉移後,不能夠再通過unique_ptr來訪問和控制資源了,否則同樣會出現程序崩潰。我們可以在使用unique_ptr訪問資源前,使用成員函數get()進行判空操作。

unique_ptr<string> upt1=std::move(upt); //控制權限轉移
if(upt.get()!=nullptr)		           //判空操作更安全
{
	//do something
}

https://www.cnblogs.com/WindSun/p/11444429.html

https://blog.csdn.net/k346k346/article/details/81478223

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章