智慧指標的那些事

程式設計-浪子發表於2015-04-17

C++不像Java,C#語言,它沒有垃圾回收機制,但是它提供了強大而靈活的管理機制,使得開發人員自己避免記憶體洩露。可以通過new 獲得記憶體或建立物件,一定使用delete來釋放,這樣就避免記憶體洩露。同時也可以將分配和使用用類封裝,從而保證沒有記憶體洩露。

#include <iostream>

using namespace std;

 

#include <stdio.h>

#include <string.h>

 

class simpleClass

{

private:

char *m_buf;

size_t m_size;

public:

simpleClass(size_t n = 1)

{

m_buf = new char[n];

m_size = n;

}

~simpleClass()

{

printf("%d is deleted at %xd \n", m_size, m_buf);

delete[] m_buf;

}

char * GetBuf()

{

return m_buf;

}

};

 

void foo()

{

simpleClass  a(10);

char *= a.GetBuf();

 

strcpy(p, "Hello");

printf("%s\n", p);

}

 

int main()

{

foo();

printf("exit main()...\n");

return 0;

}

這個程式中,對char型別的記憶體分配封裝在類simpleClass中,通過宣告物件,並給所需記憶體的大小,呼叫GetBuf獲取響應記憶體,這段記憶體在simpleClass的物件退出時會呼叫解構函式自動釋放。但是還是存在不完美的地方,如果在foo()函式中增加一條賦值語句

void foo()

{

simpleClass  a(10);

simpleClass b = a;

char *= a.GetBuf(); // 增加的語句

 

strcpy(p, "Hello");

printf("%s\n", p);

}

 

 

在實現simpleClass時,並沒有實現拷貝建構函式,因此編譯器會構造一個預設的拷貝建構函式,執行位拷貝(bit copy)操作,即物件a的內容一個個位元組的拷貝到物件b中,因此物件a中的m_buf和拷貝後物件b中的m_buf指向的是同一個地址的記憶體。當ab都銷燬時,m_buf中的內容被銷燬兩次。解決方案:

(1)禁止拷貝構造,將拷貝建構函式宣告為私有的。

(2)使用引用計數,對使用記憶體維護一個計數器。當有指標指向這塊記憶體,計數器加1,當指向這塊記憶體的指標銷燬時,計數器減1,只有當計數器減為0時,表示沒有指標指向這塊記憶體,這塊記憶體才可以被釋放。

#include <iostream>

using namespace std;

 

#include <stdio.h>

#include <string.h>

 

class simpleClass

{

private:

char *m_buf;

size_t m_size;

int *m_count;

public:

simpleClass(size_t n = 1)

{

m_buf = new char[n];

m_size = n;

 

m_count = new int; // 為m_count分配空間 

*m_count = 1;

printf("m_count is:%d\n", *m_count);

}

simpleClass(const simpleClass& s)

{

m_size = s.m_size;

m_buf = s.m_buf;

m_count = s.m_count;

(*m_count)++;

printf("m_count is:%d\n", *m_count);

}

~simpleClass()

{

(*m_count)--;

printf("m_count is:%d\n", *m_count);

if (== *m_count)

{

printf("%d is deleted at %d\n", m_size, m_buf);

delete[] m_buf;

delete m_count;

}

}

char * GetBuf()

{

return m_buf;

}

};

 

void foo()

{

simpleClass a(10);

char *= a.GetBuf();

 

strcpy(p, "Hello");

printf("%s\n", p);

 

simpleClass b = a;

printf("b=%s \n", b.GetBuf());

}

 

int main()

{

foo();

printf("exit main()...\n");

return 0;

}

 

分析:當a被構造時,m_count初始化為1,當執行b=a時,count增加為2和 b指向同一塊記憶體,儲存的內容都是“hello”。當退出foo()時,b首先被銷燬,m_count1,但是m_count的值仍是大於0的,所以記憶體沒有釋放。當a銷燬時,m_count的值減為0,才釋放對應的記憶體。

如何在foo()函式裡面新增兩句程式碼

void foo()

{

simpleClass a(10);

char *= a.GetBuf();

 

strcpy(p, "Hello");

printf("%s\n", p);

simpleClass b = a;

 

// 新增程式碼

simpleClass c(20);

= a;

printf("b= %s,c= %s\n", b.GetBuf(), c.GetBuf());

}

 

 

在通過拷貝構造建立b之後,宣告瞭一個c物件,申請的記憶體大小是20位元組。然後a賦值給c,此時c指向了a的記憶體。而c原來指向的記憶體則無指標指向,因此被釋放。但是程式沒有處理,造成記憶體洩露。解決方案,使用運算子過載。

simpleClass& operator=(const simpleClass& s)

{

// 判斷當前物件的 m_buf 是否和 s.m_buf 是否指向相同的地址 

if (m_buf == s.m_buf)

return *this;

 

// 如果不是,當前物件引用計數減1 

(*m_count)--;

 

// 如果引用計數為0,釋放該物件指向的空間 

if (*m_count == 0)

{

printf("%d is deleted at %xd\n", m_size, m_buf);

delete[] m_buf;

delete m_count;

}

// 當前物件指標 指向新的地址空間 

m_buf = s.m_buf;

m_size = s.m_size;

m_count = s.m_count;

(*m_count)++;// 這也是為什麼引用計數用指標的原因??

}

 

總算是沒有錯誤了,但是這個智慧指標還不完整,並沒有實現真正指標那樣的操作,如運算子*->都沒有過載。更多詳細內容參考《C++ Primer》 400頁。

推薦書籍:

C++ Primer(5)

*****************************分割線************************************

如果喜歡,幫忙點贊,推薦給好友

新增方式,在微信公眾號

搜尋c語言每日一問

或者Love_gcc123

或者長按二維碼圖片,就可以新增


相關文章