zoukankan      html  css  js  c++  java
  • 类的operator new与operator delete的重载

    为什么有必要写自己的operator new和operator delete?

    答案通常是:为了效率。
    缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。有一种常用的方法可以让operator new来告诉operator delete当初分配的内存大小是多少,就是在它所返回的内存里预先附带一些额外信息,用来指明被分配的内存块的大小。
    缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。

    内存池:内存池的作用主要也是为了效率。通过一次申请比较大的内存空间,来避免小空间内存的频繁申请和释放,每次需要为对象分配内存空间时,在已经申请的大空间内分配。空闲区被按照对象大小划分为若干块,块与块之间通过链表组织起来。

    参考代码:

    View Code
    #include <iostream>
    #include <string>
    using namespace std;
    
    class airplanerep  // 表示一个飞机对象
    {
    public:
        airplanerep(int id,const string & s,const string & d)
        {
            ID = id;
            start = s;
            dest = d;
        }
    
        ~airplanerep()
        {
            cout<<"airplanerep destructor!"<<endl;
        }
    
        int getID() const
        {
            return ID;
        }
    
    private:
        int ID;
        string start;
        string dest;
    };
    
    // 注意airplane和airplanerep含有一样的成员函数,它们的接口完全相同
    // 类airplane实际上是个句炳类(Handle class)
    class airplane // 修改后的类 — 支持自定义的内存管理
    {
    public:
        airplane(int id,const string & s,const string & d)
        {
            rep = new airplanerep(id,s,d);
        }
    
        ~airplane()
        {
            delete rep;
        }
    
        int getID() const
        {
            return rep->getID();
        }
    
        static void * operator new(size_t size);
        static void operator delete(void *deadobject,size_t size);
    
    private:
        union
        {
            airplanerep *rep;  // 用于被使用的对象
            airplane *next;    // 用于没被使用的(在自由链表中)对象
        };
    
        // 类的常量,指定一个大的内存块中放多少个
        // airplane对象,在后面初始化
        static const int block_size;
        static airplane *headoffreelist;
    };
    
    airplane *airplane::headoffreelist; //内存池链表头
    const int airplane::block_size = 512; //内存池块的个数
    
    void * airplane::operator new(size_t size)
    {
        // 把“错误”大小的请求转给::operator new()处理;
        if(size != sizeof(airplane))
            return ::operator new(size);
    
        airplane *p = headoffreelist; // p指向自由链表的表头
    
        // p 若合法,则将表头移动到它的下一个元素
        if(p)
            headoffreelist = p->next;
        else
        {
            // 自由链表为空,则分配一个大的内存块,
            // 可以容纳block_size个airplane对象
            airplane *newblock = static_cast<airplane*>(::operator new(block_size * sizeof(airplane)));
    
            // 将每个小内存块链接起来形成一个新的自由链表
            // 跳过第0个元素,因为它要被返回给operator new的调用者
            for (int i = 1; i < block_size-1; ++i)
                newblock[i].next = &newblock[i+1];
    
            // 用空指针结束链表
            newblock[block_size-1].next = 0;
    
            // p 设为表的头部,headoffreelist指向的
            // 内存块紧跟其后
            p = newblock;
            headoffreelist = &newblock[1];
        }
    
        return p;
    }
    
    // 传给operator delete的是一个内存块, 如果
    // 其大小正确,就加到自由内存块链表的最前面
    void airplane::operator delete(void *deadobject,size_t size)
    {
        if(deadobject == 0) return;
        if(size != sizeof(airplane))
        {
            ::operator delete(deadobject);
            return;
        }
    
        //将要释放的空间插入空闲区链表前端
        airplane *carcass = static_cast<airplane*>(deadobject);
        carcass->next = headoffreelist;
        headoffreelist = carcass;
    }
    
    int main()
    {
        airplane *pa = new airplane(101,"shanghai","beijing"); // 第一次分配: 得到大块内存,生成自由链表,等
        cout<<pa->getID()<<endl;
        delete pa;
    
        airplane *pb = new airplane(102,"shanghai","beijing");
        cout<<pb->getID()<<endl;
        delete pb;
    
        return 0;
    }

    airplane类的说明:

    airplane实际上是个句炳类(Handle class),通过指针airplanerep * rep指向一个具体的实现,airplane和airplanerep含有一样的成员函数。句柄类实际上都做了些什么:它只是把所有的函数调用都转移到了对应的主体类中,主体类真正完成工作。
    operator new函数负责内存池链表的创建,内存池链表的每个块大小和类airplane一样,每次生成对象的时候分配一个块给对象。
    operator delete函数负责收回每个对象的内存块,重新添加到内存池链表。
    operator new和operator delete需要同时工作,那么你写了operator new,就也一定要写operator delete。
    一个联合(使得rep和next域占用同样的空间),一个常量(指定大内存块的大小),一个静态指针(跟踪自由链表的表头)。表头指针声明为静态成员很重要,因为整个类只有一个自由链表,而不是每个airplane对象都有。
    注意:::operator new返回的内存块是从来没有被airplane::operator delete释放。内存泄漏和内存池有一个重要的不同之处:内存泄漏会无限地增长,而内存池的大小决不会超过客户请求内存的最大值。

    以上内容基本都来自《Effective C++》,稍作修改。

    如果类定义了自己的成员new和delete,类的用户可以通过使用全局作用域确定操作符,强制new和delete使用全局的库函数。如:
    Type *p = ::new Type;
    ::delete p;

    如果new使用全局的operator new库函数,那么对应的delete也一定要用全局的operator delete库函数。

    一个内存分配器基类

    CachedObj的功能:类似于内存池的功能。分配和管理已经分配但未构造对象的自由列表。operator new返回自由列表中的一个元素,当自由列表为空时,operator new分配新的原始内存。operator delete在撤销对象时将元素放回自由列表。

    View Code
    template<class T>
    class CachedObj
    {
    public:
        void * operator new(size_t sz);
        void operator delete(void* p, size_t sz);
        virtual ~CachedObj() {}
    protected:
        T * next;
    private:
        static void add_to_freelist(T * p);
        static allocator<T> alloc_mem;
        static T * free_store;
        static const size_t chunk;
    };
    
    template<class T>
    allocator<T> CachedObj<T>::alloc_mem;
    
    template<class T>
    T* CachedObj<T>::free_store = NULL;
    
    template<class T>
    const size_t CachedObj<T>::chunk = 64;
    
    template<class T>
    void *CachedObj<T>::operator new(size_t sz)
    {
        if(sz != sizeof(T))
            return ::operator new(sz);
    
        if (!free_store)
        {
            T * array = alloc_mem.allocate(chunk);
            for(size_t i = 0; i != chunk; ++i)
                add_to_freelist(&array[i]);
        }
    
        T *p = free_store;
        free_store = free_store->next;
        return p;
    }
    
    template<class T>
    void CachedObj<T>::operator delete(void * p,size_t sz)
    {
        if(p == NULL) return;
        if(sz != sizeof(T))
        {
            ::operator delete(p);
            return;
        }
        add_to_freelist(static_cast<T*>(p));
    }
    
    template<class T>
    void CachedObj<T>::add_to_freelist(T *p)
    {
        p->next = free_store;
        free_store = p;
    }

    如何使用这个类:

    View Code
    // 表示一个飞机对象
    class airplanerep: public CachedObj<airplanerep>
    {
    public:
        airplanerep(int id,const string & s,const string & d)
        {
            ID = id;
            start = s;
            dest = d;
        }
    
        ~airplanerep()
        {
            cout<<"airplanerep destructor!"<<endl;
        }
    
        int getID() const
        {
            return ID;
        }
    
    private:
        int ID;
        string start;
        string dest;
    };
    
    
    int main()
    {
        airplanerep *pa = new airplanerep(101,"shanghai","beijing");
        cout<<pa->getID()<<endl;
        delete pa;
    
        airplanerep *pb = new airplanerep(102,"shanghai","beijing");
        cout<<pb->getID()<<endl;
        delete pb;
    
        return 0;
    }
  • 相关阅读:
    使用element-ui组件el-table时需要修改某一行样式(包含解决样式无效的问题)或某一列的样式
    面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B
    面试题:不使用数学库求平方根
    Springboot2.x集成Redis集群模式
    Springboot2.x集成Redis哨兵模式
    Springboot2.x集成单节点Redis
    基本算法:冒泡排序算法
    Redis进阶:Redis的哨兵模式搭建
    Redis进阶:Redis的主从复制机制
    Redis的消息订阅及发布及事务机制
  • 原文地址:https://www.cnblogs.com/luxiaoxun/p/2633423.html
Copyright © 2011-2022 走看看