zoukankan      html  css  js  c++  java
  • 第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[]返回的内存空间可能比期望的要多(需额外保存数组长度等信息)

  • 相关阅读:
    以太网的寻址
    IP地址简介
    服务器控件与Html控件属性值的解释差异
    The Live Hacking CD
    德国SNS交友/视频网站Poppen.de的技术架构分享
    Forensic Log Parsing with Microsoft's LogParser
    The Flame: Questions and Answers
    hping
    WIN7与XP网络共享与访问
    Win7无法访问NAS或Samba解决之道
  • 原文地址:https://www.cnblogs.com/hoiday/p/10222382.html
Copyright © 2011-2022 走看看