C++ 11 的 unique_ptr

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 實例之間的所有權轉換。

轉移 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));    // 使用移動語義
    //vec.push_back(p.release()); //release, p 放棄控制權
 }

3.1   vector<unique_ptr<T> > 的拷貝

但是對於vector內的unique point來說,就不能簡單的使用普通迭代器了:需要使用對迭代器就行std::make_move_iterator操作:

看下英文描述最可靠:
A move_iterator is an iterator adaptor that adapts an iterator (it) so that dereferencing it produces rvalue references (as if std::move was applied), while all other operations behave the same。

簡單的說就是將vector的iterator變成make_move_iterator, 這樣源vector中的所有元素就深拷貝到目標vector中。不會導致源vector的指針問題。


#include<iostream>
#include<vector>
#include <memory>
using namespace std;
void display_vector(vector<unique_ptr<int>> &vec);
int main()
{
    vector<unique_ptr<int>> vec;
    unique_ptr<int> s1(new int(1));
    unique_ptr<int> s2(new int(2));
    unique_ptr<int> s3(new int(3));
    unique_ptr<int> s4(new int(4));
    vec.push_back(std::move(s1));
    vec.push_back(std::move(s2));
    vec.push_back(std::move(s3));
    vec.push_back(std::move(s4));


    unique_ptr<int> s5(new int(5));
    vector<unique_ptr<int>> des_vec;
    des_vec.push_back(std::move(s5));
    des_vec.insert(des_vec.end(), std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()));
    display_vector(des_vec);    
    cout << "now, des_vec size: " << des_vec.size() << endl;
    cout << "now, vec size: " << vec.size() << endl;

    return 0;
}

void display_vector(vector<unique_ptr<int>> &vec)
{
    for (auto it = vec.begin(); it != vec.end(); it++)
    {
        cout << **it << endl;
    }

//輸出結果:
5
1
2
3
4
now, des_vec size: 5
now, vec size: 4

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
 
轉載地址:http://www.cnblogs.com/DswCnblog/p/5628195.html
發佈了8 篇原創文章 · 獲贊 32 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章