智能指針的那些事

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

或者長按二維碼圖片,就可以添加


發佈了112 篇原創文章 · 獲贊 16 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章