[C++]智能指針unique_ptr,shared_ptr,weak_ptr

C++中對於動態內存的使用非常嚴格,一次申請必須對應一次釋放,否則將造成內存泄漏。這也要求程序員格外小心,比如如下示例:

void getStr() {
    std::string * pstr = new std::string();//pstr爲局部變量
    *pstr = "Hello world";
    ....
    return;
}

當該方法執行完畢後,局部變量pstr所佔用內存自動釋放,但其指向的內存則一直駐留。必須通過delete pstr來釋放,因此這裏將造成內存泄漏。

還有一種情況就是當程序因異常而終止時:

#include <stdexcept>

void getStr() {
    char * pch = new char[1024];
    ...
    if (true)
        throw logic_error("throw a exception");
    delete [] pch;
}

雖然在這裏沒有忘記delete釋放內存,但是當程序執行到throw時,將不再執行throw之後的語句,因此導致內存泄漏。針對於這個例子,雖然可以通過如下的方式避免這個問題,但依舊需要非常小心:

#include <stdexcept>

void getStr() {
    char * pch = new char[1024];
    ...
    if (true)
    try{
        throw logic_error("throw a exception");
    } catch(...){
        delete [] pch;
        throw;
    }
    delete [] pch;
}

爲了讓程序員不再因內存泄漏而操心,C++11中提供了幾個用於動態內存管理的智能指針。

智能指針的原理

如果在指針對象過期時,讓它的析構函數刪除它指向的內存,那不就避免內存的泄漏了嗎?比如當自動變量pstr被銷燬時,讓他的析構函數可以釋放它指向的內。

智能指針正是採用這種方式實現的,在智能指針類中,定義了類似指針的對象,然後將new獲得的地址賦給這個對象,當智能指針過期後,其析構函數將使用delete來釋放這塊內存。

使用智能指針

智能指針模板類位於頭文件memory中,因此使用時必須包含該頭文件。memory中提供了四種智能指針模板類:auto_ptr,unique_ptr,shared_ptr,weak_ptr,其中auto_ptr在C++11中被棄用,在C++17中被移除,因此主要學習其餘三種。

unique_ptr

unique_ptr類模板如下:

template<class T, class Deleter = std::default_delete<T>> 
class unique_ptr;

template <class T,class Deleter> 
class unique_ptr<T[], Deleter>;

因此,unique_ptr有兩個版本:

  1. 管理個對象(以 new 分配)
  2. 管理動態分配的對象數組(以 new[] 分配)

構造方法

unique_ptr的構造方法有多個,但常用如下幾個個:

constexpr unique_ptr() noexcept;
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
explicit unique_ptr(pointer p) noexcept;

unique_ptr通過建立所有權概念來管理對象,也就是說,對於特定的對象,只能有一個智能指針可以擁有它。因此:

  1. 不能使用一個unique_ptr對象來初始化另一個unique_ptr對象,其拷貝構造不可用:unique_ptr (const unique_ptr&) = delete;
  2. 不能使用一個unique_ptr對象來給另一個unique_ptr賦值,其賦值運算符不可用:unique_ptr& operator= (const unique_ptr&) = delete.

然而,當試圖將一個unique_ptr對象賦給另一個unique_ptr時,如果源unique_ptr是一個臨時右值,則編譯器允許這樣做,如:

std::unique_ptr<Person> get_uptr(Person* p) {
        std::unique_ptr<Person> temp (p);
        return temp;
}
std::unique_ptr<Person> uptr4 = get_uptr(new Person("XiaoWang"));

這是通過移動構造來區分的。

如果需要將一個unique_ptr對象賦給另一個unique_ptr,則使用std::move()函數;

成員函數

  • get():返回指向被管理對象的指針。
  • reset():替換被管理對象;
  • release(): 返回一個指向被管理對象的指針,並釋放所有權.
  • swap():替換被管理對象;
  • operator bool:檢查是否有關聯的被管理對象.

下面爲使用unique_ptr示例:

#include <iostream>
#include <memory>
#include <string>

class Person
{
private:
        std::string name;
public:
        Person(const std::string& name):name(name){
                std::cout << "Person " << name << " created." << std::endl;
        }
        void show() const {
                std::cout << "Name: " << name << std::endl;
        }
        ~Person(){
                std::cout << "Person " << name << " deleted." << std::endl;
        }
};

int main()
{
        // 1.創建unique_ptr管理Person*對象
        Person* p = new Person("XiaoQiang");
        std::unique_ptr<Person> uptr1;
        std::unique_ptr<Person> uptr2(new Person("Zhangsan"));
            
        // 2.不能用一個unique_ptr來初始化另一個unique_ptr
        //拷貝構造不可用:unique_ptr (const unique_ptr&) = delete
        //std::unique_ptr<Person> uptr3 = uptr2;
     
        // 3.同一對象只能被一個unique_ptr擁有,否則編譯出錯
        //賦值運算符不可用:operator=(const unique_ptr& ) = delete;
        //uptr1 = uptr2;

        // 3.get()返回管理對象的指針
        Person* pt = uptr2.get();
        pt->show();

        // 4.reset()替換管理對象,原來對象將銷燬
        uptr2.reset(p);
        uptr2.get()->show();

        // 5.release()返回管理對象,並釋放所有權
        uptr2.release()->show();
     
        // 6.operator bool 判斷是否有關聯對象
        if(uptr2)
                uptr2->show();
        else 
                std::cout << "uprt is null" << std::endl;
    
        // 7.swap()交換管理對象
        std::unique_ptr<Person> uptr3(new Person("LiSi"));
        std::unique_ptr<Person> uptr4(new Person("James"));
        uptr3.swap(uptr4);
        uptr3->show();

        // 8.std::move()函數將一個unique_ptr對象賦給另一個unique_ptr對象
        uptr1 = std::move(uptr4);
        uptr1->show();
        
        // 9.c++14中,使用std::make_unique(T&& t)創建unique_ptr,參數爲右值引用
        auto uptr5 = std::make_unique<Person>("LaoWang");
        uptr5->show();
        return 0;
}
/*
@ubuntu:~$ g++ uptr.cpp -o uptr --std=c++14
@ubuntu:~$ ./uptr 
Person XiaoQiang created.
Person Zhangsan created.
Name: Zhangsan
Person Zhangsan deleted.
Name: XiaoQiang
Name: XiaoQiang
uprt is null
Person LiSi created.
Person James created.
Name: James
Name: LiSi
Person LaoWang created.
Name: LaoWang
Person LaoWang deleted.
Person James deleted.
Person LiSi deleted.
*/

shared_ptr

shared_ptr通過引用計數機制來管理對象,多個 shared_ptr 對象可佔有同一對象。可以通過成員函數use_count()返回shared_ptr所指對象的引用計數。它的類模板如下:

template <class T> class shared_ptr;

構造方法

其常用構造方法如下:

constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) : shared_ptr() {}
template <class U> explicit shared_ptr (U* p);
shared_ptr (const shared_ptr& x) noexcept;
template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;
template <class U> explicit shared_ptr (const weak_ptr<U>& x);
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);

成員方法

  •     get():返回管理對象的指針;
  •     swap():交換管理對象;
  •     reset():替換管理對象,原對象將銷燬;
  •     use_count():返回shared_ptr所指對象的引用計數;
  •     unique():判斷所管理對象是否僅由當前shared_ptr的實例管理;
  •     operator bool:檢查是否有關聯的管理對象;

下面爲使用shared_ptr的示例:

#include <iostream>
#include <memory>
#include <string>

int main()
{
        std::string* str = new std::string("I'm shared_ptr.");
            
        // 1.創建shared_ptr
        std::shared_ptr<std::string> sptr1;
        std::shared_ptr<std::string> sptr2(str);
        sptr1 = sptr2;
        std::shared_ptr<std::string> sptr3(new std::string("Zhangsan"));
            
        // 2.使用右值unique_ptr創建shared_ptr
        std::shared_ptr<std::string> sptr4(std::unique_ptr<std::string>(
                        new std::string("I'm unique_ptr.")));
        std::cout << *sptr4 << std::endl;    

        // 3.get()返回管理對象的指針
        std::string* pstr = sptr1.get();
        std::cout << "*pstr: " << *pstr << std::endl;

        // 4.use_count()返回引用計數    
        std::cout << "sptr1.use_count: " << sptr1.use_count() << std::endl;
        std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;
        std::cout << "sptr3.use_count: " << sptr3.use_count() << std::endl;

        // 5.unique()檢查是否僅僅被該對象關聯
        std::cout << "sptr1.unique: " << sptr1.unique() << std::endl;
        std::cout << "sptr3.unique: " << sptr3.unique() << std::endl;
            
        // 6.operator bool檢查是否有關聯的對象
        if(sptr1)
                std::cout << *sptr1 << std::endl;
            
        // 7.reset()替換管理對象
        sptr1.reset(new std::string("I'm new shared_ptr."));
        std::cout << *sptr1 << std::endl;    

        // 8.swap()交換管理對象
        sptr1.swap(sptr3);
        std::cout << *sptr1 << std::endl;
            
        // 9.使用std::make_shared(T&& t)創建shared_ptr.
        auto sptr5 = std::make_shared<std::string>("I'm from std::makeA_shared.");
        std::cout << *sptr5 << std::endl;

        return 0;
}

/*
@ubuntu:~$ g++ shrptr.cpp -o shrptr --std=c++11
@ubuntu:~$ ./shrptr 
I'm unique_ptr.
*pstr: I'm shared_ptr.
sptr1.use_count: 2
sptr2.use_count: 2
sptr3.use_count: 1
sptr1.unique: 0
sptr3.unique: 1
I'm shared_ptr.
I'm new shared_ptr.
Zhangsan
I'm from std::makeA_shared.
*/

weak_ptr

weak_ptr是一種不控制所管理對象生存期的智能指針,也就是說,它對被管理對象具有弱引用性。它指向一個由shared_ptr管理的對象,並且將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數,一旦最後指向被管理對象的shared_ptr被銷燬,即使有weak_ptr指向對象,對象依舊被銷燬釋放。weak_ptr在訪問所引用的對象前必須先轉換爲std::shared_ptr。

構造方法

weak_ptr構造函數如下:

constexpr weak_ptr() noexcept;
template<class Y> 
weak_ptr(shared_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr const& r) noexcept;
template<class Y> 
weak_ptr(weak_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr&& r) noexcept;    // C++14
template<class Y> 
weak_ptr(weak_ptr<Y>&& r) noexcept; // C++14

成員函數

  •     use_count():返回weak_ptr所指向對象的引用計數;
  •     expired():檢查``weak_ptr是否過期,若被管理對象已被刪除則爲true,否則爲false,等價於use_count() = 0`;
  •     lock():創建新的shared_ptr對象,它共享被管理對象的所有權。若無被管理對象,即 *this 爲空,則返回亦爲空的shared_ptr.
  •     reset():釋放被管理對象的所有權;
  •     swap(weak_ptr& r):交換*this與r的內容;

使用weak_ptr示例如下:

#include <iostream>
#include <memory>
#include <string>


class Person
{
private:
        std::string name;
public: 
        Person(const std::string& str):name(str){}
        ~Person(){} 
        void show() const {
                std::cout << "Name: " << name << std::endl;
        }
};

int main()
{
        // 1.創建weak_ptr管理Person*
        std::weak_ptr<Person> wptr1;
        std::shared_ptr<Person> sptr1(new Person("Zhangsan"));
        wptr1 = sptr1;
        std::weak_ptr<Person> wptr2(wptr1);
        std::cout << sptr1.use_count() << std::endl;        

        // 2.訪問所管理對象前,必須轉換爲shared_ptr.
        // wptr1->show();//Error
        std::shared_ptr<Person> sptr2(wptr1);
        sptr2->show();
        std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;

        // 3.查看wptr1所管理對象的引用計數
        std::cout << "wptr1.use_count: " << wptr1.use_count() << std::endl;
     
        // 4.檢查是否過期
        std::weak_ptr<Person> wptr3;
        {
                auto sptr2 = std::make_shared<Person>("Lisi");
                wptr3 = sptr2;
                std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired();
                std::cout << ", use count: " << wptr3.use_count() << std::endl;
        }   
        std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired() << std::endl;

        // 5.創建新的shared_ptr共享被管理對象
        std::cout << "wptr1.use count before lock(): " << wptr1.use_count() << std::endl;
        auto p = wptr1.lock();
        std::cout << "wptr2.use count after locked(): " << wptr1.use_count() << std::endl;
        p->show();

        // 6.交換被管理對象
        auto sptr3 = std::make_shared<Person>("XiaoQiang");
        std::weak_ptr<Person> wptr4(sptr3);
        wptr1.swap(wptr4);
        std::cout << "wptr4.use_count: " << wptr4.use_count() << std::endl;

        // 7.釋放wptr1
        wptr1.reset();
        std::cout << "wptr1.expired ? " << std::boolalpha << wptr1.expired() << std::endl;

        return 0;
}

創建完成weak_ptr對象之後,使用之前,需要調用expired函數來判斷是否過期,如果沒有過期,才能調用函數rock來獲取share_ptr對象進行操作。

std::shared_ptr<string> share_str1(new string("hello world."));
std::cout << "share_str1.use_count()=" << share_str1.use_count() <<std::endl; //1
std::weak_ptr<string> weak_str1(share_str1);
std::cout << "weak_str1.use_count()=" << weak_str1.use_count() <<std::endl;//1

std::weak_ptr<string> weak_str2(weak_str1);
std::cout << "weak_str2.use_count()=" << weak_str2.use_count() <<std::endl;//1

//檢查是否過期,沒有的話,取出std::shared_ptr<string>進行操作
if(!weak_str2.expired())
{
		std::shared_ptr<string> share_str2 = weak_str2.lock();
		std::cout << "share_str2 = " << *share_str2 <<std::endl;//hello world.
}

weak_ptr的使用場景

weak_ptr最常見的用法,是和shared_ptr搭配使用,當shared_ptr用於類的成員時,如果存在循環引用,則將無法釋放內存,請看下面示例:

#include <iostream>
#include <memory>

class B;
class A
{
private:
        std::shared_ptr<B> m_sptr;
public:
        void set_sptr(std::shared_ptr<B>& sptr) {
                m_sptr = sptr;
        }
        ~A() { std::cout << "A deleted." << std::endl;}
};

class B
{
private:
        std::shared_ptr<A> m_sptr;
public:
        void set_sptr(std::shared_ptr<A>& sptr) {
                m_sptr = sptr;
        }
        ~B() { std::cout << "B deleted." << std::endl;}
};

int main()
{   
        A* a = new A; 
        B* b = new B;
        std::shared_ptr<A> asptr(a);
        std::shared_ptr<B> bsptr(b);
        std::cout << "apstr.use_count: " << asptr.use_count() << std::endl;
        std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
            
        a->set_sptr(bsptr);
        b->set_sptr(asptr);

        std::cout << "asptr.use_count: " << asptr.use_count() << std::endl;
        std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
        
        return 0;
}    
/*
@ubuntu:~$ g++ shaptr.cpp -o shaptr --std=c++11
@ubuntu:~$ ./shaptr 
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 2
bsptr.use_count: 2
*/

在以上示例中,A,B類分別帶有shared_ptr的成員,並且通過set_sptr(shared_ptr&)互相共享管理對象,那麼當程序執行完畢後,asptr和bsptr將負責釋放管理對象的內存,但此時由於引用計數爲2,因此減1,從而導致new申請的A和B的內存沒有釋放,從運行結果來看,他們的析構函數未執行。

爲避免這種問題,只需要將shared_ptr成員修改爲weak_ptr即可,因爲weak_ptr對管理對象具有弱引用性,並且將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。修改後運行結果如下:

@ubuntu:~$ ./shaptr 
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 1
bsptr.use_count: 1
B deleted.
A deleted.

auto_ptr(c++98的方案,cpp11已經拋棄)

採用所有權模式。

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的缺點是:存在潛在的內存崩潰問題!

發佈了448 篇原創文章 · 獲贊 176 · 訪問量 86萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章