智能指針unique_ptr


轉自:https://www.cnblogs.com/DswCnblog/p/5628195.html

成員函數

(1) get 獲得內部對象的指針, 由於已經重載了()方法, 因此和直接使用對象是一樣的.如 unique_ptr<int> sp(new int(1)); sp 與 sp.get()是等價的

(2) release            放棄內部對象的所有權,將內部指針置為空, 返回所內部對象的指針, 此指針需要手動釋放

(3) reset              銷毀內部對象並接受新的對象的所有權(如果使用缺省參數的話,也就是沒有任何對象的所有權, 此時僅將內部對象釋放, 並置為空)        

(4) swap               交換兩個 shared_ptr 對象(即交換所擁有的對象) std::move(up)      所有權轉移(通過移動語義), up所有權轉移后,變成“空指針” (up 的定義為 std::unique_ptr<Ty> up)

unique_ptr 不支持拷貝和賦值.

std::unique_ptr<A> up1(new A(5));   

std::unique_ptr<A> up2(up1);           // 錯誤, unique_ptr 不支持拷貝               

std::unique_ptr<A> up2 = up1;          // 錯誤, unique_ptr 不支持賦值

雖然 unique_ptr 不支持拷貝和賦值, 但是我們可以調用 release 或 reset 將指針的所有權從一個(非 const) unique_ptr 轉移到另一個.   

std::unique_ptr<int> up1(new int(1));   

std::unique_ptr<int> up2(up1.release());

雖然 unique_ptr 不支持拷貝, 但是可以從函數中返回, 甚至返回局部對象. 如下面的代碼, 編譯器知道要返回的對象即將被銷毀, 因此執行一種特殊的"拷貝":

  template <class Ty>   

  std::unique_ptr<Ty> Clone(const Ty& obj)   

  {     return std::unique_ptr<Ty>(new Ty(obj));   }
  template <class Ty>   

  std::unique_ptr<Ty> Clone(const Ty& obj)   

  {     std::unique_ptr<Ty> temp = std::unique_ptr<Ty>(new Ty(obj));     return temp;   }

 

 

unique_ptr 不共享它的指針。它無法復制到其他 unique_ptr,無法通過值傳遞到函數,也無法用於需要副本的任何標准模板庫 (STL) 算法。只能移動unique_ptr這意味着,內存資源所有權將轉移到另一 unique_ptr,並且原始 unique_ptr 不再擁有此資源。我們建議你將對象限制為由一個所有者所有,因為多個所有權會使程序邏輯變得復雜。因此,當需要智能指針用於純 C++ 對象時,可使用 unique_ptr,而當構造 unique_ptr 時,可使用make_unique Helper 函數。

std::unique_ptr實現了獨享所有權的語義。一個非空的std::unique_ptr總是擁有它所指向的資源。轉移一個std::unique_ptr將會把所有權也從源指針轉移給目標指針(源指針被置空)。拷貝一個std::unique_ptr將不被允許,因為如果你拷貝一個std::unique_ptr,那么拷貝結束后,這兩個std::unique_ptr都會指向相同的資源,它們都認為自己擁有這塊資源(所以都會企圖釋放)。因此std::unique_ptr是一個僅能移動(move_only)的類型。當指針析構時,它所擁有的資源也被銷毀。默認情況下,資源的析構是伴隨着調用std::unique_ptr內部的原始指針的delete操作的。

下圖演示了兩個 unique_ptr 實例之間的所有權轉換。

1、如何創建unique_ptr

unique_ptr不像shared_ptr一樣擁有標准庫函數make_shared來創建一個shared_ptr實例。要想創建一個unique_ptr,我們需要將一個new 操作符返回的指針傳遞給unique_ptr的構造函數。

示例:

復制代碼
int main() { // 創建一個unique_ptr實例 unique_ptr<int> pInt(new int(5)); cout << *pInt; }
復制代碼

2、無法進行復制構造和賦值操作

unique_ptr沒有copy構造函數,不支持普通的拷貝和賦值操作。

復制代碼
int main() { // 創建一個unique_ptr實例 unique_ptr<int> pInt(new int(5)); unique_ptr<int> pInt2(pInt); // 報錯 unique_ptr<int> pInt3 = pInt; // 報錯 }
復制代碼

3、可以進行移動構造和移動賦值操作

unique_ptr雖然沒有支持普通的拷貝和賦值操作,但卻提供了一種移動機制來將指針的所有權從一個unique_ptr轉移給另一個unique_ptr。如果需要轉移所有權,可以使用std::move()函數。

示例:

復制代碼
int main() { unique_ptr<int> pInt(new int(5)); unique_ptr<int> pInt2 = std::move(pInt); // 轉移所有權 //cout << *pInt << endl; // 出錯,pInt為空 cout << *pInt2 << endl; unique_ptr<int> pInt3(std::move(pInt2)); }
復制代碼

4、可以返回unique_ptr

unique_ptr不支持拷貝操作,但卻有一個例外:可以從函數中返回一個unique_ptr。

示例:

復制代碼
unique_ptr<int> clone(int p) { unique_ptr<int> pInt(new int(p)); return pInt; // 返回unique_ptr } int main() { int p = 5; unique_ptr<int> ret = clone(p); cout << *ret << endl; } 
復制代碼
復制代碼
使用舉例:
    {
        //創建一個指向int的空指針 std::unique_ptr<int> fPtr1; std::unique_ptr<int> fPtr2(new int(4)); auto fPtr3 = std::make_unique<int>(); //fPtr2釋放指向對象的所有權,並且被置為nullptr std::cout << "fPtr2 release before:" << fPtr2.get() << std::endl; int *pF = fPtr2.release(); std::cout << "fPtr2 release before:" << fPtr2.get() << " and pF value:" << *pF << std::endl; //所有權轉移,轉移后fPtr3變為空指針 std::cout << "move before fPtr1 address:" << fPtr1.get() << " fPtr3 address:" << fPtr3.get() << std::endl; fPtr1 = std::move(fPtr3); std::cout << "move after fPtr1 address:" << fPtr1.get() << " fPtr3 address:" << fPtr3.get() << std::endl; std::cout << "move before fPtr1 address:" << fPtr1.get() << std::endl; fPtr1.reset(); std::cout << "move after fPtr1 address:" << fPtr1.get() << std::endl; } 輸出:   fPtr2 release before:00EFB120   fPtr2 release before:00000000 and pF value:4   move before fPtr1 address:00000000 fPtr3 address:00EFEC60   move after fPtr1 address:00EFEC60 fPtr3 address:00000000   move before fPtr1 address:00EFEC60   move after fPtr1 address:00000000
復制代碼

 

unique_ptr使用場景

1、為動態申請的資源提供異常安全保證

我們先來看看下面這一段代碼:

復制代碼
void Func() { int *p = new int(5); // ...(可能會拋出異常) delete p; }
復制代碼

這是我們傳統的寫法:當我們動態申請內存后,有可能我們接下來的代碼由於拋出異常或者提前退出(if語句)而沒有執行delete操作。

解決的方法是使用unique_ptr來管理動態內存,只要unique_ptr指針創建成功,其析構函數都會被調用。確保動態資源被釋放。

復制代碼
void Func() { unique_ptr<int> p(new int(5)); // ...(可能會拋出異常) }
復制代碼

2、返回函數內動態申請資源的所有權

復制代碼
unique_ptr<int> Func(int p) { unique_ptr<int> pInt(new int(p)); return pInt; // 返回unique_ptr } int main() { int p = 5; unique_ptr<int> ret = Func(p); cout << *ret << endl; // 函數結束后,自動釋放資源 }
復制代碼

3、在容器中保存指針

復制代碼
int main() { vector<unique_ptr<int>> vec; unique_ptr<int> p(new int(5)); vec.push_back(std::move(p)); // 使用移動語義 }
復制代碼

4、管理動態數組

標准庫提供了一個可以管理動態數組的unique_ptr版本。

int main() { unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5}); p[0] = 0; // 重載了operator[] }

5、作為auto_ptr的替代品

創建與釋放舉例

復制代碼
#include <iostream>
#include <memory> #include <stdlib.h> struct Foo { Foo() { std::cout << "Foo::Foo\n"; } ~Foo() { std::cout << "Foo::~Foo\n"; } void bar() { std::cout << "Foo::bar\n"; } }; void f(const Foo &) { std::cout << "f(const Foo&)\n"; } struct D { void operator()(Foo* foo) { std::cout << "D operator()" << std::endl; delete foo; } }; void TestAutoDestroy() { //1. 普通的new對象. std::cout << "TestDestroy...................." << std::endl; { std::unique_ptr<Foo> p1(new Foo); } //2. 普通的new[]對象.  { std::unique_ptr<Foo[]> p2(new Foo[4]); } //3. 自定義的deleter.  { std::unique_ptr<Foo, D> p3(new Foo); } } void TestOwner() { std::cout << "TestOwner...................." << std::endl; //1. new object. std::unique_ptr<Foo> p1(new Foo); // p1 owns Foo if (p1) p1->bar(); { std::unique_ptr<Foo> p2(std::move(p1)); // now p2 owns Foo f(*p2); p1 = std::move(p2); // ownership returns to p1 p2->bar(); std::cout << "destroying p2...\n"; } p1->bar(); } void TestArrayOwner() { std::cout << "TestArrayOwner...................." << std::endl; //1. new[] object. std::unique_ptr<Foo[]> p1(new Foo[4]); // p1 owns Foo if (p1) p1[0].bar(); { std::unique_ptr<Foo[]> p2(std::move(p1)); // now p2 owns Foo f(p2[0]); p1 = std::move(p2); // ownership returns to p1 p2[0].bar(); std::cout << "destroying p2...\n"; } p1[0].bar(); } int main() { TestAutoDestroy(); TestOwner(); TestArrayOwner(); }
輸出:
TestDestroy....................
Foo::Foo
Foo::~Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::Foo
D operator()
Foo::~Foo
TestOwner....................
Foo::Foo
Foo::bar
f(const Foo&)
Foo::bar
destroying p2...
Foo::bar
Foo::~Foo
TestArrayOwner....................
Foo::Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::bar
f(const Foo&)
Foo::bar
destroying p2...
Foo::bar
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
 
復制代碼

 

一下原文:https://www.cnblogs.com/wangkeqin/p/9383658.html

一個unique_ptr"擁有“他所指向的對象。與shared_ptr不同,某個時刻只能有一個unique_ptr指向一個給定的對象。當unique_ptr被銷毀時,它所指向的對象也被銷毀。uniptr_ptr表達的是一種獨占的思想。

 

初始化

復制代碼
#include <iostream>
#include <memory>
using namespace std;

//常規操作
int main(int argc, char *argv[])
{
    unique_ptr<double> p1;               //!可指向一個double的unique_ptr
    unique_ptr<int> p2(new int(56));     //!p2指向了一個值為42的int

    unique_ptr<string> pstr(new string("strtest"));
//    unique_ptr<string> pstrCopy(pstr); //!error: 不支持對象的拷貝
    unique_ptr<string> pstrAssin;
//    pstrAssin = pstr                   //!error: uniptr不支持賦值
    return 0;
}
復制代碼

 

unique_ptr一般操作

 

  關於unique_ptr還支持哪些操作,在前面的博文中我也做了總結,請參考該篇文章中圖表:https://www.cnblogs.com/wangkeqin/p/9351191.html

 

 

 unique_ptr所有權轉移

  雖然我們不能拷貝賦值unique_ptr,但是可以通過調用release或者set將指針的所有權從一個(非const)unique_ptr轉移給一個unique:

復制代碼
#include <iostream>
#include <memory>


using namespace std;

class TEST
{
public:
    TEST(const string & name)
        :_name(name)
    {cout<<"TEST:"<<_name<<endl;}
    TEST(const TEST & another)
    {   _name = another._name;
        cout<<another._name<<" copyStruct "<<_name<<endl;}
    TEST & operator =(const TEST & another){
        if(&another==this)
            return *this;
        this->_name=another._name;
        cout<<another._name<<" copyAssin to "<<_name<<endl;
    }
    ~TEST(){cout<<"~TEST:"<<_name<<endl;}

//private:
    string _name;
};

//其他操作
int main()
{
    unique_ptr<TEST> p1(new TEST("case_1"));
    unique_ptr<TEST> p2(p1.release());          //!將所有權從p1轉移到p2,p1現在指向NULL。
    cout<<"++++++++++++++++++++++++"<<endl;
    unique_ptr<TEST> p3(new TEST("case_2"));
    p2.reset(p3.release());                     //!p2釋放了原來指向的內存,接受了p3指向的內存。
    getchar();
}
復制代碼

 

 

傳遞unique_ptr參數和返回unique_ptr

  不能拷貝unique_ptr的規則有一個例外:我們可以拷貝或者賦值一個將要被銷毀的unique_ptr。其本質就是調用了移動拷貝和移動賦值;最常見的例子是從函數返回一個unique_ptr:

復制代碼
#include <iostream>
#include <memory>


using namespace std;

class TEST
{
public:
    TEST(const string & name)
        :_name(name)
    {cout<<"TEST:"<<_name<<endl;}
    TEST(const TEST & another)
    {   _name = another._name;
        cout<<another._name<<" copyStruct "<<_name<<endl;}
    TEST & operator =(const TEST & another){
        if(&another==this)
            return *this;
        this->_name=another._name;
        cout<<another._name<<" copyAssin to "<<_name<<endl;
    }
    ~TEST(){cout<<"~TEST:"<<_name<<endl;}

//private:
    string _name;
};


//!例外:
//①返回一個即將被銷毀的uniptr
unique_ptr<TEST> retDying(string param)
{
    return unique_ptr<TEST>(new TEST(param));
}

//②返回一個局部對象;
unique_ptr<TEST> retTemp(string param)
{
    unique_ptr<TEST> pTemp(new TEST(param));
    return pTemp;
}

int main()
{
    unique_ptr<TEST>ret1 = retDying("dying");
    cout<<(*ret1)._name<<endl;

    unique_ptr<TEST>ret2 = retTemp("temp");
    cout<<(*ret2)._name<<endl;
    getchar();
}
復制代碼

 

 

向后兼容:auto_ptr

  標准庫較早的版本包含了一個名為auto_ptr的類,它具有unique_ptr的部分特性,但不是全部。特別時我們在容器中保存auto_ptr,也不能從函數中返回auto_ptr。雖然auto_ptr仍然是標准庫的一部分,但是編寫程序時應該使用unique_ptr。

 

向unique_ptr傳遞刪除器

  類似於shared_ptr,unique_ptr默認情況下也是使用delete釋放它指向的對象。與shared_ptr一樣,我們可以重載一個unique_ptr中默認的刪除器。但是unique_ptr管理刪除器的方式與shared_ptr不同,其原因我們將在后面繼續補充。

  重載一個unique_ptr中的刪除器會影響到unique_ptr類型如何構造(或reset)該類型的對象。與重載關聯器的比較操作類似。我們必須在尖括號中unique_ptr指向類型之后提供刪除器類型。在創建或者reset一個這種unique_ptr這種類型的對象時,必須提供一個指定類型的可調用對象:

 

復制代碼
#include <stdio.h>
#include <memory>
using namespace std;

void closePf(FILE * pf)
{
    cout<<"----close pf after works!----"<<endl;
    fclose(pf);
    cout<<"*****end working****"<<endl;
}

int main()
{
    //    FILE * fp2 = fopen("bin2.txt", "w");
    //    if(!pf)
    //        return -1;
    //    char *buf = "abcdefg";
    //    fwrite(buf, 8, 1, fp2);
    //    fclose(fp2);
    //______________________________________
    //    shared_ptr<FILE> pf(fopen("bin2.txt", "w"),closePf);
    //    cout<<"*****start working****"<<endl;
    //    if(!pf)
    //        return -1;
    //    char *buf = "abcdefg";
    //    fwrite(buf, 8, 1, pf.get());    //!確保fwrite不會刪除指針的情況下,可以將shared_ptr內置指針取出來。
    //    cout<<"----write int file!-----"<<endl;

    unique_ptr<FILE,decltype(closePf)*> pf(fopen("bin2.txt", "w"),closePf); //!使用了decltype類型推斷
    cout<<"*****start working****"<<endl;
    if(!pf)
        return -1;
    char *buf = "abcdefg";
    fwrite(buf, 8, 1, pf.get());                            //!確保fwrite不會刪除指針的情況下,可以將unique_ptr內置指針取出來。
    cout<<"----write int file!-----"<<endl;
    return 0;
}
復制代碼

 

使用unique_ptr管理動態數組

  標准庫提供了一個可以管理new分配動態數組的unique_ptr版本。為了用用一個unique_ptr管理動態數組,我們必須在對象類型后面跟一對空方括號;如此,在unique對象銷毀的時候,也可以自動調用delete[ ]而非delete來完成內存的釋放。

復制代碼
#include <iostream>
#include <memory>
using namespace std;

class ArrTest
{
public:
    ArrTest(){
        static int i = 0;
        _i = i;
        cout<<" ArrTest()"<<":"<<i++<<endl;
    }
    ~ArrTest(){
        static int i = 0;
        cout<<"~ ArrTest()"<<":"<<i++<<endl;
    }
    int _i;
};

int main()
{
    unique_ptr<ArrTest[]> p(new ArrTest[10]);
    cout<<p[4]._i<<endl;    //!獲取某個元素值,警告:不要使用越界的下標,unique_ptr也是不檢查越界的。
    p.reset();
    return 0;
}
復制代碼

 

 

————雁過留痕,風過留聲,人的記憶是一種很不靠譜的東西。記下這些筆記,希望自己能夠在需要的時候有所回憶,也希望能夠幫助哪些需要獲取這些知識的人。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM