C++對象池的實現和原理


什么是對象池

對象池是一種空間換時間的技術,對象被預先創建並初始化后放入對象池中,對象提供者就能利用已有的對象來處理請求,並在不需要時歸還給池子而非直接銷毀

減少對象頻繁創建所占用的內存

空間和初始化時間

對象池原理

描述一個對象池有兩個很重要的參數,一個是這個對象池的類型,另一個是這個對象池可以獲得對象的數量

 

對象池的實現和內存池的實現原理很像:都是一開始申請大內存空間,然后把大內存分配成小內存空間,當需要使用的時候直接分配使用,不在向系統申請內存空間,也不直接釋放內存空間。使用完之后都是放回池子里

 

不同的地方在內存池有一個映射數組,在使用時負責快速定位合適的內存池(一個內存池可以有很多內存塊大小不同的池子)

但是每一個類型的對象只對應一個對象池,並自己管理自己的對象池。不同類型的對象池是相互獨立的存在

 

 

 

對象池的優點

1、減少頻繁創建和銷毀對象帶來的成本,實現對象的緩存和復用

2、提高了獲取對象的響應速度,對實時性要求較高的程序有很大幫助

3、一定程度上減少了垃圾回收機制(GC)的壓力

 

對象池的缺點

1、很難設定對象池的大小,如果太小則不起作用,過大又會占用內存資源過高

2、並發環境中, 多個線程可能(同時)需要獲取池中對象, 進而需要在堆數據結構上進行同步或者因為鎖競爭而產生阻塞, 這種開銷要比創建銷毀對象的開銷高數百倍;

3、由於池中對象的數量有限, 勢必成為一個可伸縮性瓶頸;

4、所謂的臟對象就是指的是當對象被放回對象池后,還保留着剛剛被客戶端調用時生成的數據。

臟對象可能帶來兩個問題

  • 臟對象持有上次使用的引用,導致內存泄漏等問題。
  • 臟對象如果下一次使用時沒有做清理,可能影響程序的處理數據。

 

什么條件下使用對象池

1、資源受限的, 不需要可伸縮性的環境(cpu\內存等物理資源有限): cpu性能不夠強勁, 內存比較緊張, 垃圾收集, 內存抖動會造成比較大的影響, 需要提高內存管理效

率,響應性比吞吐量更為重要;

2、數量受限的, 比如數據庫連接;

3、創建對象的成本比較大,並且創建比較頻繁。比如線程的創建代價比較大,於是就有了常用的線程池。

對象池實現過程

對象池的實現代碼分三部分,一個是對象池的內部實現類(ObjectPool),另一個是管理對象池的類(ObjectPoolBase)

 

對象池內部的實現和內存池基本一樣,都是先申請一大塊內存空間,然后再把大內存分成許多小內存,每個小內存對應一個對象,這些小內存以鏈表的形式進行管理

    

 

 內存池的管理主要是對用戶提供接口,參數的傳遞通過可變參的模板類來實現,對new和delete操作進行封裝是為了更方便的初始化對象,讓使用時更加靈活;要

創建不同類型的對象池,只需要繼承ObjectPoolBase這個類就可以,當發現該類型對應的對象池沒有初始化的時候,就會創建該對象池並且進行初始化操作,之后

就可以正常分配對象

 

 

 

注意:

對象池和內存池可以一起使用,在一起使用的情況下,如果要在對象池中使用智能指針,應該特別注意new操作,因為智能指針使用的是全局的new,內存池使用的

是類里面的new,在申請內存時會有差別

 

對象池文件

#ifndef _ObjectPoolBase_hpp_
#define _ObjectPoolBase_hpp_


#include<stdlib.h>
#include<assert.h>
#include<mutex>

#ifdef _DEBUG
#ifndef xPrintf
#include<stdio.h>
#define xPrintf(...) printf(__VA_ARGS__)
#endif
#else
#ifndef xPrintf
#define xPrintf(...)
#endif
#endif // _DEBUG
//模板給對象池提供參數接口
template<class Type, size_t nPoolSzie>
//對象池的實現
class CELLObjectPool
{
public:
    CELLObjectPool()
    {
        initPool();
    }

    ~CELLObjectPool()
    {
        if (_pBuf)
            delete[] _pBuf;
    }
private:
    //NodeHeader是每個對象的描述信息
    class NodeHeader
    {
    public:
        //下一塊位置
        NodeHeader* pNext;
        //內存塊編號
        int nID;
        //引用次數
        char nRef;
        //是否在內存池中
        bool bPool;
    private:
        //預留
        char c1;
        char c2;
    };
public:
    //釋放對象內存
    void freeObjMemory(void* pMem)
    {
        //首地址往前偏移,對象和對象描述信息的內存一起釋放
        NodeHeader* pBlock = (NodeHeader*)((char*)pMem - sizeof(NodeHeader));
        xPrintf("freeObjMemory: %llx, id=%d\n", pBlock, pBlock->nID);
        assert(1 == pBlock->nRef);
        //內存在對象池的部分釋放之后,指針回到第一個未分配的對象
        if (pBlock->bPool)
        {
            std::lock_guard<std::mutex> lg(_mutex);
            if (--pBlock->nRef != 0)
            {
                return;
            }
            pBlock->pNext = _pHeader;
            _pHeader = pBlock;
        }
        else {
            if (--pBlock->nRef != 0)
            {
                return;
            }
            //不在對象池的直接釋放內存
            delete[] pBlock;
        }
    }
    //申請對象內存,優先向內存池申請內存,內存池不夠在向系統申請內存
    void* allocObjMemory(size_t nSize)
    {
        std::lock_guard<std::mutex> lg(_mutex);
        NodeHeader* pReturn = nullptr;
        //如果對象池已經滿了,數量達到上限,需要向系統申請內存
        if (nullptr == _pHeader)
        {
            //計算對象池大小=對象數量*(一個對象內存大小+對象描述信息內存大小)
            pReturn = (NodeHeader*)new char[sizeof(Type) + sizeof(NodeHeader)];
            pReturn->bPool = false;
            pReturn->nID = -1;
            pReturn->nRef = 1;
            pReturn->pNext = nullptr;
        }
        else {
            pReturn = _pHeader;
            _pHeader = _pHeader->pNext;
            assert(0 == pReturn->nRef);
            pReturn->nRef = 1;
        }
        xPrintf("allocObjMemory: %llx, id=%d, size=%d\n", pReturn, pReturn->nID, nSize);
        return ((char*)pReturn + sizeof(NodeHeader));
    }
private:
    //初始化對象池
    void initPool()
    {
        //斷言
        assert(nullptr == _pBuf);
        //對象池不為空(已經初始化過),不初始化直接返回
        if (_pBuf)
            return;
        //計算對象池大小=對象數量*(一個對象內存大小+對象描述信息內存大小)
        size_t realSzie = sizeof(Type) + sizeof(NodeHeader);
        size_t n = nPoolSzie * realSzie;
        //申請池的內存
        _pBuf = new char[n];
        //初始化內存池
        _pHeader = (NodeHeader*)_pBuf;
        _pHeader->bPool = true;
        _pHeader->nID = 0;
        _pHeader->nRef = 0;
        _pHeader->pNext = nullptr;
        //遍歷內存塊進行初始化
        NodeHeader* pTemp1 = _pHeader;

        for (size_t n = 1; n < nPoolSzie; n++)
        {
            NodeHeader* pTemp2 = (NodeHeader*)(_pBuf + (n* realSzie));
            pTemp2->bPool = true;
            pTemp2->nID = n;
            pTemp2->nRef = 0;
            pTemp2->pNext = nullptr;
            pTemp1->pNext = pTemp2;
            pTemp1 = pTemp2;
        }
    }
private:
    //描述信息塊地址
    NodeHeader* _pHeader;
    //對象池內存緩存區地址
    char* _pBuf;
    //多線程使用需要加鎖
    std::mutex _mutex;
};

//模板類給主函數使用對象池提供接口
template<class Type, size_t nPoolSzie>
//使用對象池的類
class ObjectPoolBase
{
public:
    //重載給對象申請內存的new操作
    void* operator new(size_t nSize)
    {
        return objectPool().allocObjMemory(nSize);
    }
    //重載給對象釋放內存的delete操作
    void operator delete(void* p)
    {
        objectPool().freeObjMemory(p);
    }

    //創建對象,用模板類提供對外的可變參數的接口
    //這里的模板為啥要用可變參數?
    //這里創建一個對象的時候還可能帶有初始化的參數,這個參數的個數是不一樣的,所有模板的參數需要變參
    //
    template<typename ...Args>
    static Type* createObject(Args ... args)
    {   //不定參數  可變參數
        Type* obj = new Type(args...);
        //可以做點其它的事情,比如說對象的放逐和驅逐
        return obj;
    }
    //銷毀對象
    static void destroyObject(Type* obj)
    {
        delete obj;
    }
private:
    //定義不同類型的對象池
    typedef CELLObjectPool<Type, nPoolSzie> ClassTypePool;
    //單例-餓漢模式,實例化一個對象池
    static ClassTypePool& objectPool()
    {   //靜態CELLObjectPool對象
        static ClassTypePool sPool;
        return sPool;
    }
};
#endif // !_ObjectPoolBase_hpp_
#include "ObjectPoolBase.hpp"

 

測試文件

#include<stdlib.h>
#include<iostream>
#include<thread>
#include<mutex>//
#include<memory>

#include"ObjectPoolBase.hpp"

using namespace std;

//10是池內對象的最大數量,通過繼承ObjectPoolBase來使用對象池
class ClassA : public ObjectPoolBase<ClassA, 10>
{
public:
    ClassA(int n)
    {
        num = n;
        printf("ClassA\n");
    }

    ~ClassA()
    {
        printf("~ClassA\n");
    }
public:
    int num = 0;
};

class ClassB : public ObjectPoolBase<ClassB, 10>
{
public:
    ClassB(int n, int m)
    {
        num = n * m;
        printf("ClassB\n");
    }

    ~ClassB()
    {
        printf("~ClassB\n");
    }
public:
    int num = 0;
};


int main()
{
    {
        //對象池的使用過程是在使用new的時候,因為你要創建的類繼承了對象池管理的類,
        //對象池管理類會根據你設計的對象數量創建(初始化)對象池,
        //進入對象池的時候首先判斷該對象池是否初始化,初始化過說明該對象池已經存在
        //未初始化說明還沒有該類型的對象池

        //如果該對象池沒有初始化,就進行初始化(創建一個對象池)
        //已經初始化就直接從該對象池中分配一個對象
        //然后通過new操作在對象池中分配一個已經創建好的對象
        
        //再根據對象傳進來的參數,確定對象池允許有幾個對象
        //然后申請相應大小的內存,逐個以鏈表的形式進行初始化

        //兩種創建對象的方法
        //直接用new創建對象,把new和delete暴露在外,使用不靈活,初始化對象不方便
        ClassA* a1 = new ClassA(5);
        //delete a1;

        //把new和delete封裝成一個函數,可以更靈活的對對象進行操作,比如在創建的時候初始化對象
        ClassA* a2 = ClassA::createObject(6);
        //ClassA::destroyObject(a2);

        delete a1;
        ClassA::destroyObject(a2);
        ClassB* b1 = new ClassB(5, 6);
        delete b1;

        ClassB* b2 = ClassB::createObject(5, 6);
        ClassB::destroyObject(b2);
    }
    system("pause");
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM