1. 程式人生 > >智慧指標與堆記憶體管理

智慧指標與堆記憶體管理

 

目錄

shared_ptr

std::weak_ptr

std::unique_ptr

使用時注意事項:

①.new的普通指標與shared_ptr轉換:

②.指向棧的指標與shared_ptr轉換:

3.智慧指標向常規指標的轉換


自從c++11引入智慧指標shared_ptr後,我們似乎再也不用擔心new的記憶體沒有釋放之類的問題了,但是也帶來了其他的問題。

shared_ptr

智慧指標是指向動態分配(堆)物件的指標,用於生存期控制,能確保自動正確的銷燬動態分配的物件,防止記憶體洩露。它的一種通用實現技術是使用引用計數。每次使用它,內部引用計數加1,每析構一次,內部引用計數減1,減為0時,刪去所指向的堆記憶體。

C++11中的智慧指標包括:

std::shared_ptr

std::unique_ptr

std::weak_ptr

其中最常用的就是std::shared_ptr。每個shared_ptr的拷貝都指向相同的記憶體.最後一個shared_ptr析構的時候,堆中的記憶體才會被釋放。share_ptr型別的物件具有獲得指標所有權並共享所有權的能力。

初始化:

可以通過指標來初始化它:template <class U> explicit shared_ptr (U *p);

std::shared_ptr<int>p(new int(2));

std::shared_ptr<int>p2= p;

std::shared_ptr<BaseConnector> m_connt = make_shared<Connector>(m_ios, m_strIP, m_port);

通過建構函式、賦值函式或者 make_shared 函式初始化智慧指標。

例子:

// shared_ptr constructor example
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> p1;
  std::shared_ptr<int> p2 (nullptr);
  std::shared_ptr<int> p3 (new int);
  std::shared_ptr<int> p4 (new int, std::default_delete<int>());
  std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
  std::shared_ptr<int> p6 (p5);
  std::shared_ptr<int> p7 (std::move(p6));
  std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
  std::shared_ptr<C> obj (new C);
  std::shared_ptr<int> p9 (obj, obj->data);

  std::cout << "use_count:\n";
  std::cout << "p1: " << p1.use_count() << '\n';
  std::cout << "p2: " << p2.use_count() << '\n';
  std::cout << "p3: " << p3.use_count() << '\n';
  std::cout << "p4: " << p4.use_count() << '\n';
  std::cout << "p5: " << p5.use_count() << '\n';
  std::cout << "p6: " << p6.use_count() << '\n';
  std::cout << "p7: " << p7.use_count() << '\n';
  std::cout << "p8: " << p8.use_count() << '\n';
  std::cout << "p9: " << p9.use_count() << '\n';
  return 0;
}

2. 智慧指標中的原始指標,通過 get 獲取
char* pData = pBuf.get();

3.注意事項。智慧指標雖然能自動管理堆記憶體,但他有不少陷進,使用時要注意:

     1.不要把一個原生指標給多個shared_ptr管理

     

int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error
導致ptr被刪除兩次

       2.不要把this指標賦給shared_ptr

       3. 不要在函式實參裡建立shared_ptr

         function( share_ptr<int>(new int), g( ) );//有缺陷

        可能的過程是先new int,然後調g( ),g( )發生異常,shared_ptr<int>沒有建立,int記憶體洩露

        shared_ptr<int> p(new int());
        f(p, g()); 

5.shared_ptr作為物件的成員時,小心因迴圈引用造成無法釋放資源。

struct A;
struct B;
struct A
{
std::shared_ptr<B> m_b;
};

struct B
{
std::shared_ptr<A> m_a;
};

std::shared_ptr<A> ptrA(new A);
std::shared_ptr<B> ptrB(new B);
ptrA->m_b = ptrB;
ptrB->m_a = ptrA;

ptrA和ptrB相互引用,離開作用域時引用計數都為1,導致記憶體沒有被釋放,解決辦法是把A和B任何一個的成員變數改為weak_ptr
解決辦法是把A和B任何一個的成員變數改為weak_ptr

struct B
{
std::weak_ptr<A> m_a;
};

ptrB->m_a不會增加A物件的引用計數,因此A物件離開作用域時,引用計數為0,m_b的引用計數減一,b離開作用域後引用計數由1減為0.

std::weak_ptr

弱引用指標,用來監視智慧指標,不會使應用計數加1.在訪問所引用物件前必須先轉換成std::shared.

用來管理臨時所有權的概念:當某個物件只有存在時才需要被訪問,而且隨時都可能被他人刪去,可以使用。

來跟蹤該物件。需要獲得臨時所有權時,則將其轉換為 std::shared_ptr,此時如果原來的std::shared_ptr 被銷燬,則該物件的生命期將被延長至這個臨時的 std::shared_ptr 同樣被銷燬為止。

std::unique_ptr

unique_ptr不會共享它的指標。 無法將它複製到另一個unique_ptr, unique_ptr只能移動。 這意味著記憶體資源的所有權將轉移到新的unique_ptr和原始unique_ptr不再擁有它。
int* p = new int;
std::unique_ptr<int> ptr(p);
std::unique_ptr<int> ptr1 = ptr; //不能複製,編譯報錯

auto ptr2 = std::move(ptr); //轉移所有權, 現在ptr那塊記憶體歸ptr2所有, ptr成為無效的指標.

使用時注意事項:

1.常規指標轉換為智慧指標:

①.new的普通指標與shared_ptr轉換:

如圖所示,這會發生什麼情況?答案是輸出的會是隨機數,因為經過func函式後,我們用p初始化的臨時智慧指標已經被析構了,引用計數先+1,後-1。所以經過func函式後,

p指向的物件被釋放,再解引用自然無法得到我們想要的結果。

#include<iostream>
#include <memory>
using namespace std;
void func(shared_ptr<int>)
{
    ;
}
int main()
{
    int a = 5;
    auto p = new int(5);
    func(shared_ptr<int>(p));
    cout << *p << endl;
    return 0;
}

這種情況下,正確的做法如圖所示:一開始就使用智慧指標。

#include<iostream>
#include <memory>
using namespace std;
void func(shared_ptr<int>)
{
    ;
}
int main()
{
    //int a = 5;
    auto p = make_shared<int>(5);
    func(shared_ptr<int>(p));
    cout << *p << endl;
    return 0;
}

②.指向棧的指標與shared_ptr轉換:

如圖所示,這種情況,程式會直接崩潰,因為智慧指標試圖釋放儲存在棧上的變數,它越界了

#include<iostream>
#include <memory>
using namespace std;
void func(shared_ptr<int>)
{
    ;
}
int main()
{
    int a = 5;
    auto p = &a;
    func(shared_ptr<int>(p));
    cout << *p << endl;
    return 0;
}

3.智慧指標向常規指標的轉換

我們通常使用get()函式向智慧指標索要所指向物件的擁有權,但是這樣有時也會造成錯誤:

auto p = make_shared<int>(42);
int* iPtr = p.get();
{
   shared_ptr<int>(iPtr);
}

int value = *p; // Error! 記憶體已經被釋放

p與iPtr指向了相同的記憶體,然而通過get方法後,將記憶體管理權轉移給了普通指標。iPtr傳遞給裡面程式塊的臨時智慧指標後,引用計數為1,隨後出了作用域,減少為0,釋放記憶體。