智慧指標的那些事
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
或者長按二維碼圖片,就可以新增
相關文章
- 有關指標的那些事兒《一》指標
- 關於Swift中的指標的那些事Swift指標
- Binary classification - 聊聊評價指標的那些事兒【回憶篇】指標
- Binary classification - 聊聊評價指標的那些事兒【實戰篇】指標
- 關於 智慧指標的東西指標
- 各種智慧指標的介紹指標
- 詳解c++指標的指標和指標的引用C++指標
- C++中智慧指標的簡單使用C++指標
- 【C++】智慧指標的正確使用方式C++指標
- 指向指標的指標指標
- 指標的理解指標
- 指標的用法指標
- C++中智慧指標的設計和使用C++指標
- 空指標的救星指標
- 如何理解指向指標的指標?指標
- 指標問題的一點體會(區別 [指向指標的指標] 與 [指標的指標] .) (轉)指標
- 字串指標的輸出字串指標
- 空指標的傳說指標
- const指標的引用指標
- OC指標的本質指標
- C/C++指向指標的指標C++指標
- 關於指標傳遞和指標的指標指標
- 關於COM中智慧指標的一些建議. (轉)指標
- 指標常量和常量指標的區別指標
- 指標的詳細講解指標
- 指標的刪除動作指標
- 引用與指標的區別指標
- 指標的申明和定義指標
- 智慧指標的模板,用來管理動態分配的記憶體指標記憶體
- 淺析weak指標的實現指標
- Faster\Slower 快慢指標的應用AST指標
- 陣列和指標的問題陣列指標
- 指向指標的常量引用瞭解指標
- C++ 引用計數技術及智慧指標的簡單實現C++指標
- 聊聊C語言和指標的本質C語言指標
- 二維陣列的指標的理解陣列指標
- 建立存放指標的容器並讀出指標
- JavaScript 獲取滑鼠指標的座標JavaScript指標