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 *p = 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 *p = a.GetBuf(); // 增加的語句
strcpy(p, "Hello");
printf("%s\n", p);
}
在實現simpleClass時,並沒有實現拷貝構造函數,因此編譯器會構造一個默認的拷貝構造函數,執行位拷貝(bit copy)操作,即對象a的內容一個個字節的拷貝到對象b中,因此對象a中的m_buf和拷貝後對象b中的m_buf指向的是同一個地址的內存。當a和b都銷燬時,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 (0 == *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 *p = 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。a 和 b指向同一塊內存,存儲的內容都是“hello”。當退出foo()時,b首先被銷燬,m_count減1,但是m_count的值仍是大於0的,所以內存沒有釋放。當a銷燬時,m_count的值減爲0,才釋放對應的內存。
如何在foo()函數裏面添加兩句代碼
void foo()
{
simpleClass a(10);
char *p = a.GetBuf();
strcpy(p, "Hello");
printf("%s\n", p);
simpleClass b = a;
// 添加代碼
simpleClass c(20);
c = 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
或者長按二維碼圖片,就可以添加