C++11智能指針:unique_ptr


unique_ptr

1.概念

unique_ptr形如其名,與所指對象的內存緊密地綁定,不能與其他的unique_ptr類型的指針對象共享所指向對象的內存。

在cplusplus.com中,unique_ptr聲明如下:

// non-specialized
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization	
template <class T, class D> class unique_ptr<T[],D>;

是一個模版類,T指得是指向內存的類型,D指得是deleter類型,默認為default_deleter。

請看如下例子:

int main(int argc, char* argv[]) {
    std::unique_ptr<int> u1(new int(1));
    std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
    std::unique_ptr<int> u2 = u1;        //編譯出錯
    return 0;
}

編譯結果如下:

從編譯log來看,use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’,具體原因是unique_ptr不允許與其他對象共享所指向對象的內存,已經刪除了拷貝構造函數,無法進行拷貝操作。

將上述代碼改為如下形式編譯成功:

int main(int argc, char* argv[]) {
    std::unique_ptr<int> u1(new int(1));
    std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
    std::unique_ptr<int> u2 = move(u1);
    std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
    return 0;
}

執行結果如下:

從結果可以看出u2所指向的值為1,u2所指向的地址與u1所指向的地址相同(通過get成員函數可以獲取所指向的地址)。這是由於使用了move操作,u1把內存的所有權釋放,u2獲取到內存的所有權,所以u2所指向的地址和u1所指向的地址相同。此時如果再次打印u1的值和所指向的內存地址,代碼如下:

int main(int argc, char* argv[]) {
    std::unique_ptr<int> u1(new int(1));
    std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
    std::unique_ptr<int> u2 = move(u1);
    std::cout << "u2 value : " << *u2 << '\n' << " addredd : " << u2.get() << std::endl;
    std::cout << "u1 value : " << *u1 << '\n' << " addredd : " << u1.get() << std::endl;
    return 0;
}

執行結果如下:

出現Segmentation fault的原因是u1此時以及把所指內存的所有權轉給u2,但還是打印了u1所指的值和地址。在此我就不進行core file分析了。

2.用法

1.構造函數

unique_ptr一共有8個構造函數,分別是:

描述 函數原型
1.默認構造函數 constexpr unique_ptr() noexcept;
2.初始化為空指針 constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
3.初始化為非空指針 explicit unique_ptr (pointer p) noexcept;
4.初始化為非空指針+左值deleter unique_ptr (pointer p,typename
5.初始化為非空指針+右值deleter unique_ptr (pointer p,typename remove_reference ::type&& del) noexcept;
6.移動構造函數 unique_ptr (unique_ptr&& x) noexcept;
7.非默認deleter的移動構造 template <class U, class E> unique_ptr (unique_ptr<U,E>&& x) noexcept;
8.從auto_ptr類型移動(C++17中移除掉這種用法了) template unique_ptr (auto_ptr && x) noexcept;
9.拷貝構造函數(已刪除,不可使用 unique_ptr (const unique_ptr&) = delete;

下面我們分別測試一下每種構造函數的用法:

#include <memory>
#include <iostream>

using namespace std;

class smart_point_class {
public:
    smart_point_class(int i) : _i(i) {
        cout << "Default construct..." << _i << endl;
    }
    ~smart_point_class() {
        cout << "Destroct..." << _i << endl;
    }
    inline int get_value() {
        return _i;
    }

private:
    smart_point_class(const smart_point_class&) = delete;
    smart_point_class(smart_point_class&&) = delete;

private:
    int _i;
};

class deleter {
public:
    deleter() = default;
    deleter(const deleter& other) {
        cout << "Copy deleter." << endl;
    }
    deleter(deleter&& other) {
        cout << "Move deleter." << endl;
    }
    deleter& operator=(const deleter&) {
        cout << "Copy assign deleter." << endl;
        return *this;
    }
    deleter& operator=(deleter&&) {
        cout << "Move assign deleter." << endl;
        return *this;
    }

    void operator() (smart_point_class* spc) {
        cout << "Start of deleter function : " << spc->get_value() << endl;
        delete spc;
        spc = nullptr;
        cout << "End of deleter function." << endl;
    }
};

void test_construct_fun() {
    cout << "C++ unique_ptr test function :" << endl;
    cout << "----------------------------------------------------" << endl;
    cout << "u1" << endl;
    unique_ptr<smart_point_class> u1;
    cout << "u1 : " << (u1? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u2" << endl;
    unique_ptr<smart_point_class> u2(nullptr);
    cout << "u2 : " << (u2? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u3" << endl;
    unique_ptr<smart_point_class> u3(new smart_point_class(3));
    cout << "u3 : " << (u3? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u4" << endl;
    deleter d;
    unique_ptr<smart_point_class, decltype(d)> u4(new smart_point_class(4), d);
    cout << "u4 : " << (u4? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u5" << endl;
    unique_ptr<smart_point_class, deleter> u5(new smart_point_class(5), deleter());
    cout << "u5 : " << (u5? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u6" << endl;
    unique_ptr<smart_point_class> u6 = move(u3);
    cout << "u6 : " << (u6? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u7" << endl;
    unique_ptr<smart_point_class, deleter> u7 = move(u5);
    cout << "u7 : " << (u7? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
    cout << "u8" << endl;
    unique_ptr<smart_point_class> u8 (auto_ptr<smart_point_class>(new smart_point_class(8)));
    cout << "u8 : " << (u8? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;

    cout << "Test finish..." << endl;
    cout << "u1 : " << (u1? "not null" : "null") << '\n';
    cout << "u2 : " << (u2? "not null" : "null") << '\n';
    cout << "u3 : " << (u3? "not null" : "null") << '\n';
    cout << "u4 : " << (u4? "not null" : "null") << '\n';
    cout << "u5 : " << (u5? "not null" : "null") << '\n';
    cout << "u6 : " << (u6? "not null" : "null") << '\n';
    cout << "u7 : " << (u7? "not null" : "null") << '\n';
    cout << "u8 : " << (u8? "not null" : "null") << '\n';
    cout << "----------------------------------------------------" << endl;
}

int main(int argc, char* argv[]) {
    test_construct_fun();
    return 0;
}

編譯結果如下:

會發現有個警告,是因為在C++11中不再建議使用auto_ptr了,但既然構造函數中有這個用法,我們就在這里測試一下(在C++17中已經移除掉這種用法了)。

這段代碼執行結果如下:

現在我們來分析一下執行結果:

  1. u1使用了默認構造函數,所指向內容為空;
  2. u2使用了初始化為空指針的構造函數,所指向內容為空;
  3. u3使用了初始化為非空指針的構造函數,所指向內容不為空;
  4. u4使用了初始化為非空指針+左值deleter的構造函數,會線構造一個deleter,然后拷貝這個deleter,所指向內容不為空;
  5. u5使用了初始化為非空指針+右值deleter的構造函數,會線構造一個deleter,然后移動這個deleter,所指向內容不為空;
  6. u6使用了移動構造函數,u3將所有權轉到了u6,u6不為空,但u3變為空;
  7. u7使用了自定義deleter類型的移動構造函數,u5將所有權轉到了u7,u7不為空,但u5變為空;
  8. u8使用了從auto_ptr類型移動的構造函數,不為空,但C++17已經移除了這種用法,不再建議使用;
  9. 在函數體內所有語句執行完畢之后,釋放資源。其中使用默認deleter的會調用smart_pointer_class的析構函數,不使用默認deleter的會調用deleter函數/仿函數。

2.析構函數

沒有需要特殊說明的。

3.賦值重載操作

unique_ptr一共有3個賦值重載操作,分別是:

描述 函數原型
1.移動賦值 unique_ptr& operator= (unique_ptr&& x) noexcept;
2.賦值為空 unique_ptr& operator= (nullptr_t) noexcept;
3.賦值非默認deleter的unique_ptr template <class U, class E> unique_ptr& operator= (unique_ptr<U,E>&& x) noexcept;
4.拷貝賦值函數(已刪除,不可使用) unique_ptr& operator= (const unique_ptr&) = delete;

下面我們分別測試一下每種賦值函數的用法:

#include <memory>
#include <iostream>

using namespace std;

class smart_point_class {
public:
    smart_point_class(int i) : _i(i) {
        cout << "Default construct..." << _i << endl;
    }
    ~smart_point_class() {
        cout << "Destroct..." << _i << endl;
    }
    inline int get_value() {
        return _i;
    }

private:
    smart_point_class(const smart_point_class&) = delete;
    smart_point_class(smart_point_class&&) = delete;

private:
    int _i;
};

class deleter {
public:
    deleter() = default;
    deleter(const deleter& other) {
        cout << "Copy deleter." << endl;
    }
    deleter(deleter&& other) {
        cout << "Move deleter." << endl;
    }
    deleter& operator=(const deleter&) {
        cout << "Copy assign deleter." << endl;
        return *this;
    }
    deleter& operator=(deleter&&) {
        cout << "Move assign deleter." << endl;
        return *this;
    }

    void operator() (smart_point_class* spc) {
        cout << "Start of deleter function : " << spc->get_value() << endl;
        delete spc;
        spc = nullptr;
        cout << "End of deleter function." << endl;
    }
};

void test_assign_fun() {
    cout << "C++ unique_ptr assign test :" << endl;
    cout << "u_base" << endl;
    unique_ptr<smart_point_class> u_base(new smart_point_class(1));
    cout << "u_base value : " << u_base->get_value() << '\n' << " addredd : " << u_base.get() << endl;
    cout << "----------------------------------------------------" << endl;
    cout << "u1" << endl;
    unique_ptr<smart_point_class> u1;
    u1 = move(u_base);
    cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
    cout << "----------------------------------------------------" << endl;
    unique_ptr<smart_point_class> u2(new smart_point_class(2));
    u2 = nullptr;
    cout << "----------------------------------------------------" << endl;
    unique_ptr<smart_point_class, deleter> u4;
    u4 = unique_ptr<smart_point_class, deleter>(new smart_point_class(3));
    cout << "----------------------------------------------------" << endl;
    cout << "Test finish..." << endl;
}

int main(int argc, char* argv[]) {
    test_assign_fun();
    return 0;
}

上述代碼執行結果如下:

現在我們來分析一下執行結果:

  1. u1從u_base處獲取所有權;
  2. u2使用了初始化為非空指針的構造函數,所指向內容不為空,然后賦值為空,u2 = nullptr,等同於u2.reset(),(在后面會具體說到reset的使用方法),在此進行析構操作;
  3. u3首先指向空,然后用右值賦值,調用了unique_ptr的移動賦值函數;
  4. 所有語句執行完成之后,釋放資源。u3會調用deleter函數/仿函數,u2在之前已經釋放,最后釋放u1,用默認的deleter。

4.get()

get函數會返回存儲的指針。如果由unique_ptr不為空,則存儲的指針指向由unique_ptr管理的對象,否則指向nullptr。需要注意的是調用這個函數並不會釋放指針的所有權(它仍然負責刪除管理的數據)。因此,此函數的返回值不用於構建新的指針。如果要獲取存儲的指針並釋放所有權,調用unique_ptr::release()。

get函數使用方法如下:

int main(int argc, char* argv[]) {
    int *i = new int(1);
    cout << "i : " << i << endl;
    unique_ptr<int> u(i);
    cout << "u value : " << *u << '\n' << "u address : " << u.get() << endl;
    return 0;
}

該段代碼執行結果如下:

從圖中看出可以正確打印出地址。

5.get_deleter()

get_deleter函數會返回deleter。deleter是一個可調用的對象。使用成員類型指針的單個參數對該對象的函數調用將刪除托管對象,並在unique_ptr本身被銷毀、分配了一個新值或在非空時重置時自動調用。unique_ptr模板使用的默認deleter類型是default_delete,一個無狀態類。

get_deleter使用例子:

int main(int agrc, char* argv[]) {
    // smart_point_class和deleter在之前的代碼中有定義
    unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
    smart_point_class* p(new smart_point_class(2));

    cout << "----------------------------------------------------" << endl;
    u.get_deleter()(p);
    cout << "----------------------------------------------------" << endl;
}

上述代碼執行結果如下:

在兩條分割線之間的語句u.get_deleter()(p),就等同於調用deleter函數/仿函數。

6.bool操作重載

沒有需要特殊說明的。

7.release()

該操作會釋放指針的所有權,並通過返回值返回。此調用不會銷毀托管對象,但unique_ptr對象將從刪除對象的責任中釋放。某些其他實體必須負責刪除對象。

請看如下代碼:

int main(int agrc, char* argv[]) {
    unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
    u.release();
}

上述代碼執行結果如下:

會發現並沒有對u存儲的指針進行銷毀的操作,這樣非常容易造成內存泄漏。代碼應該改為如下形式:

int main(int agrc, char* argv[]) {
    unique_ptr<smart_point_class, deleter> u(new smart_point_class(1), deleter());
    smart_point_class *p = u.release();
    delete p;
}

上述代碼執行結果如下:

8.reset()

銷毀當前由unique_ptr管理的對象(如果有的話),並獲取p的所有權。如果p是一個空指針(例如默認初始化的指針),unique_ptr將變為空,在調用之后不管理任何對象。若要釋放存儲指針的所有權而不破壞它,請使用成員函數release。

release使用例子:

int main(int agrc, char* argv[]) {
    unique_ptr<smart_point_class> u1(new smart_point_class(1));
    cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
    smart_point_class *p (new smart_point_class(2));
    cout << "----------------------------------------------------" << endl;
    u1.reset(p);
    cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
    cout << "----------------------------------------------------" << endl;
    u1.reset();
    cout << "----------------------------------------------------" << endl;
}

上述代碼執行結果如下:

  1. 在執行完u1.reset(p)語句之后,會首先銷毀資源,然后u1所管理內存的value由1變為2,對應的地址也有了改變;
  2. 在執行完u1.reset()語句之后,會銷毀u1現在所管理內存的資源。

9.swap()

交換兩個unique_ptr所管理的對象。

swap使用例子:

int main(int agrc, char* argv[]) {
    unique_ptr<smart_point_class> u1(new smart_point_class(1));
    unique_ptr<smart_point_class> u2(new smart_point_class(2));
    cout << "----------------------------------------------------" << endl;
    cout << "Before swap :" << endl;
    cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
    cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
    cout << "----------------------------------------------------" << endl;
    u1.swap(u2);
    cout << "After swap :" << endl;
    cout << "u1 value : " << u1->get_value() << '\n' << " addredd : " << u1.get() << endl;
    cout << "u2 value : " << u2->get_value() << '\n' << " addredd : " << u2.get() << endl;
    cout << "----------------------------------------------------" << endl;
}

上述代碼執行結果如下:

可以看到u1和u2所管理的對象做了交換操作。

10. * ->操作

與普通指針操作相同,在此不再贅述。


免責聲明!

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



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