69_技巧:自定義內(nèi)存管理

關(guān)鍵詞: mutable处渣、new/deletenew[]/delete[]操作符重載

1. 問題:統(tǒng)計對象中某個成員變量的訪問次數(shù)栽惶。

知識點補充:mutable關(guān)鍵字
(1) mutable 是為了突破const函數(shù)的限制而設(shè)計的
(2) mutable成員變量永遠處于可改變的狀態(tài)
(3) mutable在實際的項目開發(fā)中被嚴(yán)禁濫用

mutable的深入分析
(1) mutable成員變量破壞了只讀對象的內(nèi)部狀態(tài)
(2) const 成員函數(shù)保證只讀對象的狀態(tài)不變性
(3)mutable成員變量的出現(xiàn)無法保證狀態(tài)不變性

編程說明:成員變量的訪問統(tǒng)計——使用mutable

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    mutable int m_count;
public:
    Test(int value = 0)
    {
        m_value = value;
        m_count = 0;
    }
    
    int getValue() const
    {
        m_count++;
        
        return m_value;
    }
    
    void setValue(int value)
    {
        m_count++;
        
        m_value = value;
    }
    
    int getCount() const
    {
        return m_count;
    }
};


int main()
{
    Test t;
    
    t.setValue(100);
    
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl; 
    
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl; 
    
    return 0;
}

輸出結(jié)果:

t.m_value = 100
t.m_count = 2
ct.m_value = 200
ct.m_count = 1

在實際項目中禁止使用mutable關(guān)鍵字,因此可以不使用mutable關(guān)鍵字來統(tǒng)計成員變量的訪問芍殖。

編程說明:成員變量的訪問統(tǒng)計——通過指針常量來統(tǒng)計

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int m_value;
    int* const m_pCount;    // 定義一個指針常量豪嗽,指針地址不會改變,但指針?biāo)赶虻闹悼梢愿淖?public:
    Test(int value = 0) : m_pCount(new int(0))  // 初始化指針?biāo)赶虻闹禐?
    {
        m_value = value;
    }
    
    int getValue() const
    {
        *m_pCount = *m_pCount + 1;
        
        return m_value;
    }
    
    void setValue(int value)
    {
        *m_pCount = *m_pCount + 1;
        
        m_value = value;
    }
    
    int getCount() const
    {
        return *m_pCount;
    }
    
    ~Test()      // 釋放指針?biāo)赶虻目臻g
    {
        delete m_pCount;  
    }
};


int main()
{
    Test t;
    
    t.setValue(100);
    
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_count = " << t.getCount() << endl; 
    
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_count = " << ct.getCount() << endl; 
    
    return 0;
}

輸出結(jié)果:

t.m_value = 100
t.m_count = 2
ct.m_value = 200
ct.m_count = 1

2. new關(guān)鍵字創(chuàng)建出來的對象位于什么地方豌骏?

補充知識點:
(1) new/delete的本質(zhì)是C++預(yù)定義的操作符——可以進行操作符重載
(2) C++對這兩個操作符做了嚴(yán)格的行為定義:
new:
1. 獲取足夠大的內(nèi)存空間(默認為堆空間)
2. 在獲取的空間中調(diào)用構(gòu)造函數(shù)創(chuàng)建對象
delete:
1. 調(diào)用析構(gòu)函數(shù)銷毀對象
2. 歸還對象所占用的空間(默認為堆空間)

  • 在C++中能夠重載new/delete操作符

    • 全局重載【不推薦】
    • 局部重載(針對具體類進行重載)
  • 重載new/delete的意義:改變動態(tài)對象創(chuàng)建時的內(nèi)存分配方式

  • newdelete的重載方式
    newdelete的重載函數(shù)都是類的成員函數(shù)龟梦,這兩個函數(shù)默認為靜態(tài)成員函數(shù)static member function,(函數(shù)聲明中static關(guān)鍵字可以省略窃躲,都已經(jīng)默認為靜態(tài)成員函數(shù))

// static member function
void* operator new (unsigned int size)    // size為指定內(nèi)存空間大小
{
    void* ret = NULL;
    // 1. ret指向內(nèi)存空間
    // 2. 調(diào)用構(gòu)造函數(shù)计贰,創(chuàng)建對象
    return ret;
}

// static member function
void operator delete(void* p)  
{
    // 1. 調(diào)用析構(gòu)函數(shù),銷毀對象
    // 2. 歸還對象所占用的空間
}

編程說明:在通過new在靜態(tài)存儲區(qū)創(chuàng)建對象,重載了newdelete操作符

#include <iostream>

using namespace std;

class Test
{
    /* 在靜態(tài)存儲區(qū)里開發(fā)動態(tài)空間 */
    static const unsigned int COUNT = 4;    // 定義存儲對象的個數(shù)COUNT 
    static char c_buffer[];     // 定義靜態(tài)存儲區(qū)
    static char c_map[];        // 定義標(biāo)記數(shù)組蒂窒,用于標(biāo)記自定義的區(qū)域哪些位置已經(jīng)創(chuàng)建對象
    int m_value;
public:
    void* operator new (unsigned int size)
    {
        void* ret = NULL;

        for(int i=0; i<COUNT; ++i)
        {
            if( !c_map[i] )
            {
                c_map[i] = 1;       // 將對應(yīng)區(qū)域改為已占用
                ret = c_buffer + sizeof(Test) * i;  // 將返回值指針指向可用區(qū)域
                cout << "succeed to allocate memory: " << ret << endl;
                break;
            }
        }
        
        return ret;
    }

    void operator delete(void* p)
    {
        if( p != NULL )
        {
            char* mem = reinterpret_cast<char*>(p);      // 將需要釋放的指針轉(zhuǎn)化為char類型躁倒,為下一步的指針運算做準(zhǔn)備
            int index = (mem - c_buffer) / sizeof(Test); // 確定區(qū)域位置 
            int flag = (mem - c_buffer) % sizeof(Test);  // 通過余數(shù)判斷此次傳來的指針是否合法,若flag==0代表指針合法洒琢,若flag!=0代表指針不合法

            if( (flag == 0) && (index >= 0) && (index < COUNT) )    // 判斷指針是否合法秧秉,且是否在給定區(qū)域
            {
                c_map[index] = 0;   // 將指定區(qū)域標(biāo)記為可用

                cout << "succeed to free memory : " << p << endl;
            }
        }
    }
};

char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};  // 在靜態(tài)存儲區(qū)分配所需空間,并將空間初始化為0
char Test::c_map[COUNT] = {0};  // 初始化標(biāo)記數(shù)組衰抑,0代表空間未使用象迎,1代表空間已使用

int main()
{
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    delete pt;

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

    for( int i=0; i<5; ++i)
    {
        pa[i] = new Test;
        cout << "p[" << i << "] = " << pa[i] << endl;
    }

    for( int i=0; i<5; ++i)
    {
        cout << "delete " << pa[i] << endl;
        delete pa[i];
    }

    return 0;
}

3. 面試題:在指定地址(棧空間/堆空間/靜態(tài)存儲區(qū))上創(chuàng)建c++對象?

解決方案:

  • 在類中重載new/delete操作符
  • new的操作符重載函數(shù)中返回指定的地址
  • delete操作符重載中標(biāo)記對應(yīng)的地址可用
#include <iostream>
#include <cstdlib>

using namespace std;

/* 動態(tài)指定在(靜態(tài)存儲區(qū)/棧/堆)空間中創(chuàng)建對象 */
class Test
{
    static char* c_buffer;          // 定義存儲區(qū)
    static char* c_map;             // 定義標(biāo)記數(shù)組呛踊,用于標(biāo)記自定義的區(qū)域哪些位置已經(jīng)創(chuàng)建對象
    static unsigned int c_count;    // 定義存儲對象的個數(shù)c_count
    int m_value;
public:
    static bool setMemorySource(char* memory, unsigned int size)    // 動態(tài)指定具體內(nèi)存中創(chuàng)建對象
    {
        bool ret = false;
        
        c_count = size / sizeof(Test);  // 計算空間大小砾淌,c_count 
        
        ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));    // 當(dāng)c_count>0時, 創(chuàng)建c_map
        if( ret )   // 當(dāng) count>0 且 c_map 創(chuàng)建成功時
        {
            c_buffer = memory;      // 將 c_buffer 指定到 memory 上
        }
        else        // 否則谭网, 一切清零
        {
            free(c_map);
            
            c_map = NULL;
            c_buffer = NULL;
            c_count = NULL;
        }
        
        return ret;
    }

    void* operator new (unsigned int size)
    {
        void* ret = NULL;

        if( c_count > 0 )       // c_count>0:即指定的內(nèi)存空間標(biāo)記數(shù)組長度大于0時汪厨,可以在指定的內(nèi)存空間中創(chuàng)建對象
        {
            for(int i=0; i<c_count; ++i)
            {
                if( !c_map[i] )
                {
                    c_map[i] = 1;       // 將對應(yīng)區(qū)域改為已占用
                    ret = c_buffer + sizeof(Test) * i;  // 將返回值指針指向可用區(qū)域
                    cout << "succeed to allocate memory: " << ret << endl;
                    break;
                }
            }
        }
        else
        {
            ret = malloc(size);     // 在堆空間中創(chuàng)建對象    
        }
        
        return ret;
    }

    void operator delete(void* p)
    {
        if( c_count > 0 )   // c_count>0:在指定的內(nèi)存空間中銷毀給定對象
        {
            if( p != NULL )
            {
                char* mem = reinterpret_cast<char*>(p);      // 將需要釋放的指針轉(zhuǎn)化為char類型,為下一步的指針運算做準(zhǔn)備
                int index = (mem - c_buffer) / sizeof(Test); // 確定區(qū)域位置 
                int flag = (mem - c_buffer) % sizeof(Test);  // 通過余數(shù)判斷此次傳來的指針是否合法蜻底,若flag==0代表指針合法骄崩,若flag!=0代表指針不合法

                if( (flag == 0) && (index >= 0) && (index < c_count) )  // 判斷指針是否合法聘鳞,且是否在給定區(qū)域
                {
                    c_map[index] = 0;   // 將指定區(qū)域標(biāo)記為可用

                    cout << "succeed to free memory : " << p << endl;
                }
            }
        }
        else
        {
            free(p);        // 在堆空間中銷毀給定對象
        }
    }
};

unsigned int Test::c_count = 0; // 初始化指定內(nèi)存空間數(shù)組大小
char* Test::c_buffer = NULL;    // 初始化指定內(nèi)存空間
char* Test::c_map = NULL;       // 初始化標(biāo)記數(shù)組

/* ====== 測試部分 ====== */

/* 在給定堆空間創(chuàng)建對象 */
void test_create_object_in_heap()
{
    cout << "========== test_create_object_in_heap ==========" << endl;
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

    for( int i=0; i<5; ++i)
    {
        pa[i] = new Test;
        cout << "p[" << i << "] = " << pa[i] << endl;
    }

    for( int i=0; i<5; ++i)
    {
        cout << "delete " << pa[i] << endl;
        delete pa[i];
    }
}

/* 在給定棧空間創(chuàng)建對象 */
void test_create_object_in_stack()
{
    cout << "========== test_create_object_in_stack ==========" << endl;
    
    char space[sizeof(Test) * 4];           // 在棧內(nèi)存中分配空間
    
    Test::setMemorySource(space, sizeof(space));
    
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

    for( int i=0; i<5; ++i)
    {
        pa[i] = new Test;
        cout << "p[" << i << "] = " << pa[i] << endl;
    }

    for( int i=0; i<5; ++i)
    {
        cout << "delete " << pa[i] << endl;
        delete pa[i];
    }
}

/* 在給定靜態(tài)存儲區(qū)空間創(chuàng)建對象 */
void test_create_object_in_static_area()
{
    cout << "========== test_create_object_in_static_area ==========" << endl;
    
    static char space[sizeof(Test) * 4];        // 在靜態(tài)存儲區(qū)分配空間
    
    Test::setMemorySource(space, sizeof(space));
    
    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test;
    cout << "pt" << pt << endl;
    cout << "delete pt" << pt << endl;
    delete pt;
    

    cout << "===== Test Object Array =====" << endl;
    Test* pa[5] = {0};
    

    for( int i=0; i<5; ++i)
    {
        pa[i] = new Test;
        cout << "p[" << i << "] = " << pa[i] << endl;
    }

    for( int i=0; i<5; ++i)
    {
        cout << "delete " << pa[i] << endl;
    }   
}

int main()
{
    test_create_object_in_heap();
    test_create_object_in_stack();
    test_create_object_in_static_area();

// Test的sizeof為4要拂,那么sizeof代表的值是非靜態(tài)成員變量的值抠璃;
// 在堆空間,椡讯瑁空間搏嗡,靜態(tài)區(qū)的內(nèi)存大小是一致的,只是存放的方式不一致
//  cout << sizeof(Test) << endl;       // 4
    return 0;
}

輸出結(jié)果:

========== test_create_object_in_heap ==========
===== Test Single Object =====
pt0x9bd3008
delete pt0x9bd3008
===== Test Object Array =====
p[0] = 0x9bd3008
p[1] = 0x9bd3018
p[2] = 0x9bd3028
p[3] = 0x9bd3038
p[4] = 0x9bd3048
delete 0x9bd3008
delete 0x9bd3018
delete 0x9bd3028
delete 0x9bd3038
delete 0x9bd3048
========== test_create_object_in_stack ==========
===== Test Single Object =====
succeed to allocate memory: 0xbfd8a3ec
pt0xbfd8a3ec
delete pt0xbfd8a3ec
succeed to free memory : 0xbfd8a3ec
===== Test Object Array =====
succeed to allocate memory: 0xbfd8a3ec
p[0] = 0xbfd8a3ec
succeed to allocate memory: 0xbfd8a3f0
p[1] = 0xbfd8a3f0
succeed to allocate memory: 0xbfd8a3f4
p[2] = 0xbfd8a3f4
succeed to allocate memory: 0xbfd8a3f8
p[3] = 0xbfd8a3f8
p[4] = 0
delete 0xbfd8a3ec
succeed to free memory : 0xbfd8a3ec
delete 0xbfd8a3f0
succeed to free memory : 0xbfd8a3f0
delete 0xbfd8a3f4
succeed to free memory : 0xbfd8a3f4
delete 0xbfd8a3f8
succeed to free memory : 0xbfd8a3f8
delete 0
========== test_create_object_in_static_area ==========
===== Test Single Object =====
succeed to allocate memory: 0x804b101
pt0x804b101
delete pt0x804b101
succeed to free memory : 0x804b101
===== Test Object Array =====
succeed to allocate memory: 0x804b101
p[0] = 0x804b101
succeed to allocate memory: 0x804b105
p[1] = 0x804b105
succeed to allocate memory: 0x804b109
p[2] = 0x804b109
succeed to allocate memory: 0x804b10d
p[3] = 0x804b10d
p[4] = 0
delete 0x804b101
delete 0x804b105
delete 0x804b109
delete 0x804b10d
delete 0

4. new[]delete[]

  • 動態(tài)對象數(shù)組創(chuàng)建通過new[]完成
  • 動態(tài)對象數(shù)組銷毀通過delete[]完成
  • new[]/delete[]能夠被重載拉一,進而改變內(nèi)存管理方式
    new[]/delete[]的重載方式
// static member function
void* operator new[] (unsigned int size)    
{
    return malloc(size);
}

// static member function
void operator delete(void* p)  
{
    free(p);
}

注意事項:

  • new[]實際需要返回的內(nèi)存空間可能比期望的要多
  • 對象數(shù)組占用的內(nèi)存中需要保存數(shù)組信息
  • 數(shù)組信息用于確定構(gòu)造函數(shù)析構(gòu)函數(shù)的調(diào)用次數(shù)

編程說明:new/delete與new[]/delete[]調(diào)用

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
    int m_value;
public:
    Test()
    {
        m_value = 0;
    }
    
    ~Test()
    {
    }

    void* operator new (unsigned int size)
    {
        cout << "operator new: " << size << endl;
        
        return malloc(size);
    }

    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        
        free(p);
    }
    
    void* operator new[] (unsigned int size)
    {
        cout << "operator new[]: " << size << endl;
        
        return malloc(size);
    }

    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        
        free(p);
    }
};

int main()
{
    Test* pt = NULL;
    pt = new Test;
    delete pt;
    
    pt = new Test[5];
    delete[] pt;
    
    return 0;
}

5. 小結(jié)

  • new/delete的本質(zhì)為操作符
  • 可以通過全局函數(shù)重載new/delete(不推薦使用)
  • 可以針對具體的類重載new/delete
  • new[]/delete[]new/delete完全不同
  • new[]/delete[]可以進行操作符重載
  • new[]返回的內(nèi)存空間可能比期望的要多(包含了數(shù)組的長度信息)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末采盒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔚润,更是在濱河造成了極大的恐慌磅氨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫡纠,死亡現(xiàn)場離奇詭異烦租,居然都是意外死亡,警方通過查閱死者的電腦和手機除盏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門叉橱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人者蠕,你說我怎么就攤上這事窃祝。” “怎么了踱侣?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵粪小,是天一觀的道長。 經(jīng)常有香客問我抡句,道長糕再,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任玉转,我火速辦了婚禮突想,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘究抓。我一直安慰自己猾担,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布刺下。 她就那樣靜靜地躺著绑嘹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪橘茉。 梳的紋絲不亂的頭發(fā)上工腋,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天姨丈,我揣著相機與錄音,去河邊找鬼擅腰。 笑死蟋恬,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趁冈。 我是一名探鬼主播歼争,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼渗勘!你這毒婦竟也來了沐绒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旺坠,失蹤者是張志新(化名)和其女友劉穎乔遮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體取刃,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡申眼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蝉衣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡巷蚪,死狀恐怖病毡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屁柏,我是刑警寧澤啦膜,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站淌喻,受9級特大地震影響僧家,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裸删,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一八拱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涯塔,春花似錦肌稻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榛搔,卻和暖如春诺凡,著一層夾襖步出監(jiān)牢的瞬間东揣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工腹泌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘶卧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓真屯,卻偏偏與公主長得像脸候,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绑蔫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359