第69课 技巧:自定义内存管理

1. 遗失的关键字:mutable

(1)mutable关键字

  ①mutable是为了突破const函数的限制而设计的

  ②mutable成员变量永远处于可改变的状态

  ③mutable实际的项目开发中被严禁滥用

(2)深入分析mutable

  ①mutable成员变量破坏了只读对象的内部状态

  ②const成员函数保证只读对象的状态不变性

  ③mutable成员变量的出现无法保证状态不变性

【编程实验】成员变量的访问统计

方法1:利用mutable关键字来实现

#include <iostream>



using namespace std;



class Test
{

    int m_value;



    //mutable关键字,表示该变量是可以改变的,哪怕

    //在const函数中,也可以被改变!

    mutable int m_count; // 统计m_value被使用的次数

public:

    Test(int value=0)
    {

        m_value = value;

        m_count = 0;

    }



    //const成员函数目的是让普通对象和const对象都可以访问

    int getValue() const //注意,是const成员函数

    {

        ++m_count; //m_count被mutable修饰,表示

                   //该变量哪怕在const函数中也可被改变!



        return m_value;

    }



    void setValue(int value)

    {

        ++m_count;

        m_value = value;

    }



    //const成员函数目的是让普通对象和const对象都可以访问

    int getCount() const

    {

        return m_count;

    }

};



int main()

{

    Test t; //普通对象

    t.setValue(100); //第1次访问t.m_value

    cout << "t.m_value = " << t.getValue() << endl; //第2次访问t.m_value

    cout << "t.m_count = " << t.getCount() << endl; //t.m_count = 2;



    const Test ct(200); //const对象

    cout << "ct.m_value = " << ct.getValue() << endl; //第1次访问ct.m_value

    cout << "ct.m_count = " << ct.getCount() << endl; //ct.m_count = 1;



    return 0;

}

运行结果:

  

方法2:利用指针或指针常量来实现

#include <iostream>



using namespace std;



class Test

{

    int m_value;



    //m_pCount可以是一般的成员变量,而不一定非得是const成员变量
    int*  const m_pCount; //统计m_value被使用的次数

public:

    Test(int value=0):m_pCount(new int(0))

    {

        m_value = value;

    }



    //const成员函数目的是让普通对象和const对象都可以访问

    int getValue() const //注意,是const成员函数

    {

        *m_pCount =*m_pCount + 1;



        return m_value;

    }



    void setValue(int value)

    {

        *m_pCount =*m_pCount + 1;

        m_value = value;

    }



    //const成员函数目的是让普通对象和const对象都可以访问

    int getCount() const

    {

        return *m_pCount;

    }

};



int main()

{

    Test t; //普通对象

    t.setValue(100); //第1次访问t.m_value

    cout << "t.m_value = " << t.getValue() << endl; //第2次访问t.m_value

    cout << "t.m_count = " << t.getCount() << endl; //t.m_count = 2;



    const Test ct(200); //const对象

    cout << "ct.m_value = " << ct.getValue() << endl; //第1次访问ct.m_value

    cout << "ct.m_count = " << ct.getCount() << endl; //ct.m_count = 1;



    return 0;

}

运行结果:

  

2. 被忽略的事实:new关键字创建的对象位于什么地方?

(1)new/delete关键字,其本质是C++预定义的操作符,因此支持重载

(2)C++对这两个操作符做了严格的行为定义

   关键字

主要行为的定义

new

①获取足够大的内存空间(默认为堆空间)

②在获取的空间中调用构造函数创建对象

delete

调用析构函数销毁对象

归还对象所占用的空间(默认为堆空间)

(3)C++中new/delete操作符重载两种方式

  全局重载会影响所有的类(不推荐)

  局部重载:针对具体类进行重载

(4)重载new/delete的意义:在于改变动态对象创建时的内存分配方式

(5)new/delete的重载示例

//new/delete会被默认的定义为静态成员函数,哪怕在定义时没有显式指出。因为在调用new的时候,对象还
//没创建出来,所以只能通过静态成员函数来调用。

//静态成员函数(即使没写static,也会被自动声明为static函数)
void* operator new (unsigned int size)
{

    void* ret = NULL;

    /*ret指向一片刚分配好的内存*/

    return ret;

}


//静态成员函数
void operator delete(void* p)
{

    /*释放p所指定的内存空间*/

}

(6)注意:

  ①直接使用new关键字直接调用Test::operator new(...)函数是有区别的。前者会调用Test类的构造函数,而后者函数调用方式则不会

  ②同理,直接使用delete关键字直接调用Test::operator delete(…)函数时,前者会调用析构函数,后者不会

【编程实验】静态存储区中创建动态对象

#include <iostream>



using namespace std;



class Test

{

    //存储在静态区中Test对象的最大数量
    static const unsigned int COUNT = 4;

    //用于存储Test对象的静态内存区,注意为static变量
    static char c_buffer[];

    //用于标识内存空间的使用情况
    static char c_map[];



public:



    //重载new操作符,将对象分配在静态区(而非堆上!)
    void* operator new (unsigned int size) //默认为静态函数
    {

        void* ret = NULL;

        //查找静态区中的内存空闲

        for(int i=0; i<COUNT; i++)

        {

            if(!c_map[i]) //空闲时

            {

                c_map[i] = 1; //标志为正在使用



                ret = c_buffer + i * sizeof(Test);

                cout << "success to allocate memory: " << ret << endl;



                break;

            }

        }



        return ret; //当返回后,编译器继继会生成调用构造函数来初始化

                    //这片内存空间的代码。

    }



    //重载delete操作符,将释放对应的内存空间(标志为空闲)
    void operator delete (void* p)
    {

        if( p != NULL)

        {

            char* mem = reinterpret_cast<char*>(p);

            int index = (mem - c_buffer) / sizeof(Test);

            int flag = (mem - c_buffer) % sizeof(Test);//传入的是对象地址?



            if( (flag == 0) && (0 <= index) && (index < COUNT) )

            {

                c_map[index] = 0; //标记为空闲

            }



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

        }



        //函数返回后,编译器自动生成调用析构函数的代码
    }

};



char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};
char Test::c_map[Test::COUNT] = {0};



int main()
{

    cout << "===== Test Single Object =====" << endl;
    Test* pt = new Test; //相当于(Test*)Test::operator new(sizeof(Test));
    delete pt;



    cout << "===== Test Object Array =====" << endl;
Test
* pa[5] = {0}; //模拟内存管理,因内存区最多只能分配4个Test对象,当申请第5个对象 //时,会返回NULL。这个例子也可结合二阶构造来生成《多例模式》 for( int i=0; i<5; i++) { pa[i] = new Test; cout << "pa[" <<i <<"] = " << pa[i] << endl; } //释放 for( int i=0; i<5; i++) { cout << "delete " << pa[i] << endl; delete pa[i]; } return 0; }

/*输出结果

===== Test Single Object =====

success to allocate memory: 0x406038

succeed to free memory: 0x406038

===== Test Object Array =====

success to allocate memory: 0x406038

pa[0] = 0x406038

success to allocate memory: 0x406039

pa[1] = 0x406039

success to allocate memory: 0x40603a

pa[2] = 0x40603a

success to allocate memory: 0x40603b

pa[3] = 0x40603b

pa[4] = 0    //第5个对象分配失败

delete 0x406038

succeed to free memory: 0x406038

delete 0x406039

succeed to free memory: 0x406039

delete 0x40603a

succeed to free memory: 0x40603a

delete 0x40603b

succeed to free memory: 0x40603b

delete 0

*/

【心得体会】

  new的两个主要任务:1.分配内存,2.调用构造函数初始化。而上例子中的operator new函数却只见分配内存,不见初始化工作,这看起来有点不可思议。其实重载的operator new函数确实只需完成前一半的功能,那初始化工作在什么时候实现的呢?答案是当调用new Test时,会先调用operator new,之后编译器自动地为我们插入了初始化的代码。这点也可以从Test* pt = new Test和 Test* pt =  Test::operator new(...)表现出来的行为不同看出来。前者会调用构造函数,而后者不会,只是一个普通的函数调用。可见operator new并未完全地重载new的两个功能,否则这两个调用结果应该是完全一样的。 这也是new操作符与一般的操作符重载的不同之处。

3.在指定地址上中创建C++对象的解决方案

(1)在类中重载new/delete操作符

(2)在new操作符重载函数返回指定的地址

(3)在delete操作符重载标记对应的地址可用

【编程实验】自定义动态对象的存储空间

#include <iostream>

#include <cstdlib> //for calloc函数

 

using namespace std;

 

class Test

{

    //内存中存储Test对象的最大数量
    static unsigned int c_count;

    //用于存储Test对象的内存区
    static char* c_buffer;

    //用于标识内存空间的使用情况
    static char* c_map;

  
    int m_value;

   

public:

    //设置将对象存储在指定的内存地址处
    static bool SetMemorySource(char* memory, unsigned int size)

    {

        bool ret = false;

       

        c_count  = size / sizeof(Test); //计算对象的个数

       

        //calloc会初始化内存为0

        ret = ((memory != NULL) && c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));

   

        if (ret)
        {

            c_buffer = memory;

        }
        else
        {

            free(c_map);

            c_map = NULL;

            c_buffer = NULL;

            c_count = 0;

        }

       

        return ret;

    }

 

    //重载new操作符

    void* operator new (unsigned int size)

    {

        void* ret = NULL;

       

        //1.先在指定的地址空间中分配对象

        if(c_count > 0)

        {

            //查询指定内存是否有空闲

            for(int i=0; i< c_count; i++)

            {

                if(!c_map[i]) //空闲

                {

                    c_map[i] = 1;//标记为正在使用

                   

                    ret = c_buffer + i * sizeof(Test);

                   

                    cout << "succeed to allocate memory: " << ret << endl;

                   

                    break;

                }

                //空闲己满,直接返回NULL

            }

        }

        else

        {

            //当没有调用SetMemorySource来要求在指定的地址上分配对象(默认情况)

            //此时直接在堆上创建对象。

            ret = malloc(size);

            cout << "allocate in Heap memory: " << ret << endl;

        }

       

        return ret;

    }

   

    //重载delete操作符

    void operator delete(void* p)

    {

        if ( p!=NULL )

        {

            //在指定地址分配对象

            if( c_count> 0)

            {

                char* mem = reinterpret_cast<char*>(p);

                int index = (mem - c_buffer) / sizeof(Test);

                int flag = (mem - c_buffer) % sizeof(Test);

               

                if( (flag == 0) && (0 <= index) && (index < c_count))

                {

                    c_map[index] = 0; //标记为空闲

                   

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

                }

            }

            else

            {

                //默认是在堆上创建对象

                free(p);

                cout << "free Heap memory: " << p << endl;

            }     

        }

    }

};

 

unsigned int Test::c_count = 0;

char* Test::c_buffer = NULL;

char* Test::c_map = NULL;

 

int main()
{

    //默认行为

    cout << "==== Default Behavior======" << endl;

    Test* pt = new Test; //默认是在堆上创建的

    delete pt;

   

    //指定new出来的对象分配在栈上

    char buffer[12] = {0};

    Test::SetMemorySource(buffer, sizeof(buffer)); //指定在栈上分配对象(最多3个对象)

   

    cout << "==== Test Single Object======" << endl;

    pt = new Test; //分配在栈上

    delete pt;

   

    cout << "==== Test Object Array======" << endl;

    Test* pa[5] = {0};
   
    //分配5个对象,但只有前3个会成功,后面两个返回NULL
    for(int i=0; i<5; i++)
    {

        pa[i] = new Test;

        cout << "pa[" << i << "] = " << pa[i] << endl;

    }


    for(int i=0; i<5; i++)
    {

        cout << "delete " << pa[i] << endl;

        delete pa[i];

    }

   

    return 0;

}

/*输出结果:

==== Default Behavior======

allocate in Heap memory: 0x5929c8

free Heap memory: 0x5929c8

==== Test Single Object======

succeed to allocate memory: 0x23fe98

succeed to free memory: 0x23fe98

==== Test Object Array======

succeed to allocate memory: 0x23fe98

pa[0] = 0x23fe98

succeed to allocate memory: 0x23fe9c

pa[1] = 0x23fe9c

succeed to allocate memory: 0x23fea0

pa[2] = 0x23fea0

pa[3] = 0

pa[4] = 0

delete 0x23fe98

succeed to free memory: 0x23fe98

delete 0x23fe9c

succeed to free memory: 0x23fe9c

delete 0x23fea0

succeed to free memory: 0x23fea0

delete 0

delete 0

*/

4.new[]和delete[]操作符

(1)new[]/delete[]new/delete完全不同。可以将new[]new看作是两个完全不同的的操作符。同理delete[]delete也是两个完全不同的操作符

  ①动态对象数组的创建通过new[]完成

  ②动态对象数组的销毁通过delete[]完成

(2)new[]/delete[]能够被重载,进而改变内存管理方式如在堆或静态区中分配内存

(3)new[]和delete[]的重载示例

//静态成员函数
void* operator new[] (unsigned int size)
{

    return malloc(size);

}

 

//静态成员函数
void operator delete(void* p)
{

    free(p);

}

(4)注意事项

  ①new[]实际需要返回的内存空间可能比期望的要多(因为需额外保存数组长度等信息

  ②对象数组占用的内存中需要保存数组信息

  ③数组信息用于确定构造函数和析构函数的调用次数

  ④直接使用new[]关键字与直接使用Test::operator new[](...)函数是有区别的。前者会调用Test类的构造函数,而后者函数调用方式则不会

  ⑤同理,直接使用delete[]关键字和直接使用Test::operator delete[](…)函数时,前者会调用析构函数,后者不会

【编程实验】动态数组的内存管理

#include <iostream>

#include <cstdlib> //for malloc函数

 

using namespace std;

 

class Test

{

    int m_value;

public:

    Test()

    {

        m_value = 0;

        cout <<"Test()" << endl;

    }

    ~Test()

    {

        cout <<"~Test()" << endl;

    }

   

    //重载new操作符
    void* operator new (unsigned int size)

    {

        void* ret = NULL;

        ret = malloc(size);

        cout <<"operator new: " << ret << "size: " <<size << endl;

        return ret;

    }

    //重载delete操作符
    void operator delete (void* p)

    {

        cout <<"operator delete: " << p << endl;

        free(p);

    }

   

    //重载new[]操作符
    void* operator new[] (unsigned int size)

    {

        void* ret = NULL;

        ret = malloc(size);

        cout <<"operator new[]: " << ret << " size: " <<size << endl;

        return ret;

    }

    //重载delete[]操作符
    void operator delete[] (void* p)
    {

        cout <<"operator delete[]: " << p << endl;

        free(p);

    }   

  

};

 

int main()
{

    Test* pt = NULL;

    pt = new Test;//注意使用new关键字与直接使用Test::operator new(...)函数是有区别的。

                  //new Test会调用Test的构造函数,而函数调用方式则不会。

    delete pt;

   

    //当用new Test[5]时,只须传入数组元素的个数,编译器会向operator new[](...)函数中

    //传入的参数为5*sizeof(Test) + sizeof(unsigned int),其中的sizeof(unsigned int)为额外

    //空间,用于保存元素的个数。同理new Test[5]与直接使用Test::operator new[](...)是有区别的。

    //前者会调用构造函数,后者则不会。

    pt = new Test[5];//相当于Test::operator new(5*sizeof(Test) + sizeof(unsigned int))

    delete[] pt;

   

    return 0;

}

/*输出结果:

operator new: 0x5529c8size: 4

Test()

~Test()

operator delete: 0x5529c8

operator new[]: 0x5529c8 size: 24

Test()

Test()

Test()

Test()

Test()

~Test()

~Test()

~Test()

~Test()

~Test()

operator delete[]: 0x5529c8

*/

5. 小结

(1)new/delete本质为操作符

(2)可以通过全局函数重载new/delete(不推荐)

(3)可以针对具体的类重载new/delete

(4)new[]/delete[]new/delete完全不同

(5)new[]/delete[]也是可以被重载的操作符

(6)new[]返回的内存空间可能比期望的要多(需额外保存数组长度等信息)

原文地址:https://www.cnblogs.com/hoiday/p/10222382.html