1. 程式人生 > 實用技巧 >a review at smart pointer(4)

a review at smart pointer(4)

來源 https://www.cnblogs.com/jiayayao/p/6128877.html

#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>

using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
    std::shared_ptr<Person> p1(new Person(1));// Person(1)的引用計數為1
    std::shared_ptr<Person> p2 = std::make_shared<Person>(2);
    p1.reset(new Person(3));// 首先生成新物件,然後引用計數減1,引用計數為0,故析構Person(1)
                            // 最後將新物件的指標交給智慧指標
    std::shared_ptr<Person> p3 = p1;//現在p1和p3同時指向Person(3),Person(3)的引用計數為2
    p1.reset();//Person(3)的引用計數為1
    p3.reset();//Person(3)的引用計數為0,析構Person(3)
    return 0;
}
  • 首先我們解釋一下什麼是引用計數,首先初始化一個shared ptr p1,然後其中的成員是new Person(1)
  • 記憶體佈局可以理解為,一個heap區域的person類其地址為(假設為hp1),然後一個shared ptr在stack區,叫做p1
  • 然後同樣的:heap區域的person類person(2)和其地址為hp2,然後一個shared ptr在stack區:p2
  • p1 reset一個new person(3), 這個的解釋過程為:首先在heap區new一個person 3,其地址為hp3,然後,執行reset函式,因為reset的目的永遠是釋放一個count,所以count - 1變成0,然後析構person(1),所以,hp1消失,然後hp3交給p1,count自增
  • 然後複製一個shared ptr p3,值是p1,裡面的內容也是一樣的, 我們發現hp3既交給了p1也交給了p3,count為2
  • p1.reset, count--
  • p3.reset, count--

所以我們可以發現,share ptr的share為什麼是share? 它代表的是多個share指標共享一個堆資源

之後我有個碩大的疑問

  • 來源某pp prime的一句話:shared_ptr自動銷燬所管理的物件
    當指向一個物件的最後一個shared_ptr被銷燬時,shared_ptr類會自動銷燬此物件,它是通過另一個特殊的成員函式-解構函式完成銷燬工作的,類似於建構函式,每個類都有一個解構函式。解構函式控制物件銷燬時做什麼操作。解構函式一般用來釋放物件所分配的資源。shared_ptr的解構函式會遞減它所指向的物件的引用計數。如果引用計數變為0,shared_ptr的解構函式就會銷燬物件,並釋放它所佔用的記憶體。
  • 然後不僅僅是這一個部落格上有說:不要用一個原始指標初始化多個shared_ptr,原因在於,會造成二次銷燬
    int *p5 = new int;
    std::shared_ptr<int> p6(p5);
    std::shared_ptr<int> p7(p5);// logic error
  • 上述程式碼很好理解,二次銷燬誰不知道

我的問題是,這篇文章的第一份程式碼不也是兩個指標指向一個資源嗎?為什麼這個就沒有錯呢?

答案是:第一份程式碼是兩個智慧指標有著一模一樣的值,它是shared ptr(語義上),指向一個資源,它們在stack區域的值是一模一樣的,=之間應該是有運算子過載的機制讓count自增(代表了shared,所以count自增)

但是第二個例子不對的地方在於,它並不是智慧指標的賦值,它是讓一個指標進入兩個建構函式,所以count應該是無自增的機制的

第一個例子由shared ptr自己的機制進行垃圾回收,根據count何時減到0

  • 另外,不要在函式實參中建立shared_ptr
// 錯
function(shared_ptr<int>(new int), g());

通過查閱文件我們發現unique ptr的預設刪除器是支援釋放陣列物件的,如: std::unique_ptr<int[]> foo (new int[5]); 但是shared ptr 不支援[] ,所以我們要自定義deleter

關於到底是int[]還是int,自定義(不自定義)deleter的問題,我直說了: 我看不懂,因為20,17,11三個版本的要求全不一樣,感興趣的參見這篇部落格 https://www.cnblogs.com/apocelipes/p/10346928.html 到時候開發就現查文件

  • 另外,注意unique ptr可以轉shared ptr,反過來不可,比如
#include <iostream>
#include <memory>
using namespace std;

class A{
public:
    string id;
    A(string id):id(id){cout<<id<<":建構函式"<<endl;}
    ~A(){cout<<id<<":解構函式"<<endl;}
};

int main() {
    unique_ptr<A> a(new A("unique_ptr"));
    shared_ptr<A> b = move(a);
//    a = move(b);  // 報錯
//    a.reset(b.get());  // 執行錯誤
    cout<<a.get()<<endl;
    return 0;
}
  • 用get拿到智慧指標的裸指標之後刪掉它,會導致智慧指標執行錯誤

  • 不要用stack中的變數地址初始化一個smart pointer

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

class A{
public:
    string id;
    A(string id):id(id){cout<<id<<":建構函式"<<endl;}
    ~A(){cout<<id<<":解構函式"<<endl;}
};

A a("全域性變數");

int main() {
    A b("區域性變數");
//    unique_ptr<A> pa(&a); // 執行錯誤
    unique_ptr<A> pa(&b);
    return 0;
}

謹慎使用智慧指標的get與release方法

  • 通過unique_ptr.release()方法返回的裸指標,需要我們自己delete刪除物件,因為呼叫release方法後,該unique_ptr不再擁有物件的所有權。

迴圈引用問題待更