智能指標的那些事,智能指標那些事
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
或者長按二維碼圖片,就可以添加