Way to C++: 3. std::unique_ptr

·

2 min read

前言

上一篇

在一個 class 裡面,有一個 raw pointer 還好,假如有兩個 raw pointer (都是要 new 的那種)呢?

class A {  
public:
    A() {  
        // ...  
        b = new int;  
        c = new int;  
        // ...
    }
    // ... other function
    int *b, *c;
};

這樣寫就會造成一個重要的問題,c++ 在 new 時,其實有可能會因為系統空間不夠, new 不出來,丟出 Exception 。Exception 會中斷當前的 code ,直到有外層的 try-catch 抓起來。

假如在 b new 完,在 c new 時出現 Exception,那 b 就會 memory leak。

一種方法是在每個 new 時都用 try-catch 包起來,好好的處理前面的資源:

b = new int;

try {
    c = new int;
} catch (...) { 
    delete b;
    throw;
}

但這種方法完全很冗,有更多 resource 會讓 code 裡面有很多重複的地方。

所以我們會需要 1 個 class 管理 1 個指標(或資源),讓他在丟 Exception 跳離當前 block 時觸發解構子回收資源。

std::unique_ptr

std::unique_ptr 就可以幫我們做的這件事情,這 class 管理的 raw pointer 會在 destructor 觸發時刪除。

{
    std::unique_ptr<int> a(new int(5));  
} // 回收!

同樣的問題,裡面管理的指標,要怎麼「複製」? unique_ptr 是不管這塊的,應該說,複製 unique_ptr instance 被禁止了,只留下搬移,也就是說,只允許只有一個 unique_ptr instance 管理這個指標。

所以回來看一下 IntBuff 可以怎麼改寫,雖然 unique_ptr 只允許搬移,但是 IntBuff 的複製仍然可以實作。

#include <algorithm>
#include <cstdio>
#include <memory>

class IntBuff {
public:
    IntBuff() = default;
    explicit IntBuff(size_t sz) : sz(sz) {
        if (sz) {
            arr = std::unique_ptr<int[]>(new int[sz]);
        }
    };

    IntBuff(const IntBuff& ib) {
        sz = ib.sz;
        arr = std::unique_ptr<int[]>(new int[sz]);
        for (int lx = 0; lx < sz; lx++) {
            arr[lx] = ib.arr[lx];
        }
    }

    IntBuff& operator=(const IntBuff& ib) {
        IntBuff tmp(ib);  // copy-and-swap
        std::swap(tmp.arr, arr);
        std::swap(tmp.sz, sz);
        return *this;
    }

    IntBuff(IntBuff&& ib) {
        arr = std::move(ib.arr);
        sz = ib.sz;
        ib.sz = 0;
    }

    IntBuff& operator=(IntBuff&& ib) {
        arr = std::move(ib.arr);
        sz = ib.sz;
        ib.sz = 0;
        return *this;
    }

private:
    std::unique_ptr<int[]> arr = nullptr;
    size_t sz = 0;
};

int main() {
    {
        IntBuff a1(size_t(10)), b1;
        b1 = std::move(a1);
    }
    return 0;
}

基本上, delete 都可以省去,交由 std::unique_ptr 本身來管理。在複製時,就是直接開一個新的 unique_ptr ,和原本的 code 邏輯基本上是一樣的。

基本上, IntBuff 到這裡就算是完善了。