1. 程式人生 > 實用技巧 >淺談C++的智慧指標

淺談C++的智慧指標

C++11STL中新增了智慧指標,可以有效的防止記憶體洩露的問題,以下程式碼除特殊說明均在C++11下編譯。

標頭檔案

#include <memory>

shared_ptr 共享指標

共享指標可以方便的在不同函式中傳遞,並且在所有指標例項被銷燬時自動釋放空間。
shared_ptr通過引用計數來管理引用狀態,每次有新的引用時計數加1,有引用銷燬是計數減1,直到計數為0時自動釋放記憶體空間。

只要將new運算子返回的指標p交給一個shared_ptr物件“託管”,就不必擔心在哪裡寫delete p語句——實際上根本不需要編寫這條語句,託管pshared_ptr物件在消亡時會自動執行delete p


媽媽再也不用擔心我忘記delete啦

通過shared_ptr的建構函式,傳入讓shared_ptr託管的指標。
例如:

shared_ptr<int> p(new int);

在函式見傳遞shared_ptr,示例程式碼:

#include <memory>
#include <cstdio>
using namespace std;

struct node {
    int a, b, c;
    char e;
};

int foo(shared_ptr<node> a) {
    // p2被傳入foo,a指向p2,引用計數加1,現在為3
    return a->a + a->b;
    // foo函式結束,a被釋放,引用計數減1,現在為2
}

// 聽說從main函式開始閱讀程式是一個好習慣
int main() {
    shared_ptr<int> p(new node); // p引用node物件的例項,引用計數現在為1
    shared_ptr<int> p2 = p;      // p2引用p,引用計數加1,現在為2

    foo(p2);
    return 0;
    // main函式結束,p,p2被釋放,引用計數減2,現在為0
    // 引用計數為0,自動釋放node的例項。
}

shared_ptr可以通過get成員函式獲取普通指標:

shared_ptr<int> p(new node);
printf("%p\n", p.get()); // 輸出p指向的例項的地址

shared_ptr過載了->*運算子,可以直接訪問目標物件:

shared_ptr<int> p(new node);
p->a = 5;
printf("%d\n", p->a);
printf("%d\n", (*p).a);

在C++17中從過載[]運算子,用於訪問陣列:

shared_ptr<int[]> p(new int[105]);
p[1] = 1;
p[2] = 5;
for (int i = 3; i <= 100; ++i)
    p[i] = i;

int sum = 0;
for (int i = 3; i <= 100; ++i)
    sum += p[i];

什麼,你想在C++11中用[]運算子?那麼,有請下一位出場。

unique_ptr 單引用指標

unique_ptr一樣會自動釋放記憶體,但是隻能用一個unique_ptr指向目標,其他的都會失效。
Rust中的所有權機制一樣。

在函式見傳遞unique_ptr,示例程式碼:

#include <memory>
#include <cstdio>
using namespace std;

struct node {
    int a, b, c;
    char e;
};

unique_ptr<node> foo(unique_ptr<node> a) {
    // p2被傳入foo,a獲取p2的所有權,p2失效。
    a->a += a->b;
    return a;
    // foo函式結束,但是a被作為返回值返回,a擁有所有權。
}

void bar(unique_ptr<node> &a) {
    // p2被傳入foo,a是p2的引用,p2擁有所有權。
    a->a += a->c;
    return;
    // foo函式結束,a被銷燬,p2擁有所有權。
}

// 聽說從main函式開始閱讀程式是一個好習慣
int main() {
    unique_ptr<int> p(new node); // p引用node物件的例項。
    unique_ptr<int> p2 = p;      // p2獲取p的所有權,現在p失效。
    printf("%p\n", p.get()); // p已經失效,輸出0x00000000。
    printf("%p\n", p1.get()); // 輸出p2指向的地址。

    // 下面是兩種函式傳遞unique_ptr的方式
    p2 = foo(p2); // foo函式返回a,p2重新取回所有權。
    bar(p2);
    return 0;
    // main函式結束,p2被銷燬,記憶體自動釋放。
}

unique_ptr可以通過get成員函式獲取普通指標:

unique_ptr<int> p(new node);
printf("%p\n", p.get()); // 輸出p指向的例項的地址

unique_ptr過載了->*運算子,可以直接訪問目標物件:

unique_ptr<int> p(new node);
p->a = 5;
printf("%d\n", p->a);
printf("%d\n", (*p).a);

unique_ptr從過載[]運算子,用於訪問陣列:

shared_ptr<int[]> p(new int[105]);
p[1] = 1;
p[2] = 5;
for (int i = 3; i <= 100; ++i)
    p[i] = i;

int sum = 0;
for (int i = 3; i <= 100; ++i)
    sum += p[i];

weak_ptr 反向指標

不知道大家在之前看shared_ptr時是否發現,如果有兩個物件相互引用,他們的引用計數永遠不會歸0,他們永遠不會被釋放,產生迴圈引用

-------------------                  -------------------
| class A         |                  | class B         |
|-----------------|                  |-----------------|
|int x;           |  use each other  |int y;           |
|shared_ptr<B> b<--------------------->shared_ptr<A> a |
|-----------------|                  |-----------------|

這時就需要weak_ptr出場了:

-------------------                  -------------------
| class A         |                  | class B         |
|-----------------|                  |-----------------|
|int x;           |one direction use |int y;           |
|shared_ptr<B> b---------------------->weak_ptr<A> a   |
|-----------------|                  |-----------------|

這是因為weak_ptr的引用不會被shared_ptr計算。