C++ 实现对象池的具体方法

 更新时间:2022年1月17日 16:24  点击:282 作者:Alfred-N

前言

需求无限,但资源有限的情况下,就需要对资源进行专门的管理。不断的申请和释放内存是不合理的,会造成内存的波动,以及内存不受限的增长。比如,实现了一个消息队列,当发消息的速度快于处理消息的速度时,如果不对资源进行控制,就会导致内存不断的增长。除非有专门的内存管理机制,或明确的编译器优化内存复用,否则建立一个资源管理模块是很有必要的。对象池就是一个对限定数量资源复用管理的模块。

一、什么是对象池

复用对象,消除频繁的对象创建销毁带来的性能消耗,以及避免内存增长的不可控。比如,线程池、连接池都是为了实现复用对象。
举个例子:假设在生产者消费者模型中,生产者生产时创建对象,消费者消费后销毁对象。直接简单的使用new和delete,就会让对象频繁创建和销毁导致额外性能消耗,而且生产者速度大于消费者速度时,就会让对象数量创建大于销毁导致内存不受控制增长。如果使用对象池,就可以让生产和消费复用固定数量的对象,很好的避免了频繁创建销毁对象以及内存增长不受控制的情况。

二、如何实现

1.确定接口

(1)、确定动态关系
通过序列图可以确定对象需要的接口,我们以socket服务为场景绘制序列图,如下

在这里插入图片描述

(2)、确定静态关系
根据上面的序列图确定的接口绘制成类图,如下:

在这里插入图片描述

2.转成代码

由于模块规模小,接口也不多,所以就不展示进一步细化设计了。因为本文讲述的是C++实现对象池,所以将上述设计转化为C++接口定义。如下:

    /// <summary>
	/// 对象池
	/// </summary>
	class ObjectPool
	{
	public:		
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="bufferArray">对象池的缓冲区,由外部指定,可以理解为一个数组。数组大小需满足bufferSize>=elementSize*arraySize</param>
		/// <param name="elementSize">数组元素大小</param>
		/// <param name="arraySize">数组长度或元素个数</param>
		ObjectPool(void* bufferArray, int elementSize, int arraySize );		
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate();
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate(int timeout);	
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(void* element);	
	};

三、完整代码

根据上述的初步设计,再进行细化,以及实现,最终得出如下代码实现。
ObjectPool.h

#ifndef OBJECTPOOL_H
#define OBJECTPOOL_H
/************************************************************************
* @Project:  	AC::ObjectPool
* @Decription:  对象池:“需求很大,但数量有限”的情况下,就需要对资源进行专门的管理,
*不断的申请和释放对象是不合理的(除非有专门的内存管理机制,或明确的编译优化内存复用)。
*这是一个对限定数量资源的复用管理模块。
* @Verision:  	v1.0.0.1
* @Author:  	Xin Nie
* @Create:  	2018/12/21 13:34:00
* @LastUpdate:  2022/1/5 13:53:00
************************************************************************
* Copyright @ 2022. All rights reserved.
************************************************************************/
#include<unordered_map>
#include<vector>
#include<mutex>
#include<condition_variable>
namespace AC {
	/// <summary>
	/// 对象池
	/// </summary>
	class ObjectPool
	{
	public:		
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="bufferArray">对象池的缓冲区,由外部指定,可以理解为一个数组。数组大小需满足bufferSize>=elementSize*arraySize</param>
		/// <param name="elementSize">数组元素大小</param>
		/// <param name="arraySize">数组长度或元素个数</param>
		ObjectPool(void* bufferArray, int elementSize, int arraySize );
		/// <summary>
		/// 析构方法
		/// </summary>
		~ObjectPool();
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate();
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		void* Applicate(int timeout);	
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(void* element);	
		/// <summary>
		/// 获取对象池的缓冲区,即构造方法中的bufferArray
		/// </summary>
		/// <returns>缓冲区的指针</returns>
		void* GetPoolBuffer();
		/// <summary>
		/// 获取对象的大小,即构造方法中的elementSize
		/// </summary>
		/// <returns>对象的大小</returns>
		int GetObjectSize();
		/// <summary>
		/// 获取总的对象数量,即构造方法中的arraySize
		/// </summary>
		/// <returns>总的对象数量</returns>
		int GetObjectCount();
	private:
		void*_buffer = NULL;
		int _elementSize;
		int _arraySize;
		std::vector<void*> _unusedUnits;
		std::unordered_map<void*, int> _usedUnits;
		std::mutex _mutex;
		std::condition_variable _cond;
	};

	/// <summary>
	/// 泛型对象池
	/// </summary>
	/// <typeparam name="T">对象类型</typeparam>
	template<typename T>
	class ObjectPoolGeneric:private ObjectPool
	{
	public:
		/// <summary>
		/// 构造方法
		/// </summary>
		/// <param name="array">对象数组</param>
		/// <param name="size">数组大小</param>
		/// <returns></returns>
		ObjectPoolGeneric(T*array,int size) :ObjectPool(array, sizeof(T), size)
		{
		}
		/// <summary>
		/// 析构方法
		/// </summary>
		~ObjectPoolGeneric() {}
		/// <summary>
		/// 申请对象
		/// 如果池里对象不足,则会等待,直到有对象才返回。
		/// </summary>
		/// <returns>返回申请的对象指针</returns>
		T* Applicate() {
			return (T*)ObjectPool::Applicate();
		}
		/// <summary>
		/// 申请对象
		/// </summary>
		/// <param name="timeout">超时时间,超时后返回null</param>
		/// <returns>返回申请的对象指针</returns>
		T* Applicate(int timeout) {
			return (T*)ObjectPool::Applicate(timeout);
		}
		/// <summary>
		/// 归还对象
		/// </summary>
		/// <param name="element">需归还的对象</param>
		void ReturnBack(T* element)
		{
			ObjectPool::ReturnBack(element);
		}
		/// <summary>
		/// 获取对象池的缓冲区,即构造方法中的bufferArray
		/// </summary>
		/// <returns>缓冲区的指针</returns>
		T* GetPoolBuffer() {
			return (T*)ObjectPool::GetPoolBuffer();
		}
	};
}
#endif 

ObjectPool.cpp

#include "ObjectPool.h"
#include <chrono> 
namespace AC {
	ObjectPool::ObjectPool(void* bufferArray, int elementSize, int arraySize)
	{
		if (elementSize < 1 || arraySize < 1)
			return;
		_buffer = bufferArray;
		_elementSize = elementSize;
		_arraySize = arraySize;
		char* firstAdress = (char*)bufferArray;
		//记录未使用的索引
		for (int i = 0; i < arraySize; i++)
		{
			_unusedUnits.push_back(&(firstAdress[i * elementSize]));
		}
	}
	ObjectPool::~ObjectPool()
	{
	}
	void* ObjectPool::Applicate()
	{
		return Applicate(-1);
	}
	void* ObjectPool::Applicate(int timeout) {
		void* resource = NULL;
		std::unique_lock<std::mutex> l(_mutex);
		while (_unusedUnits.size() < 1)
		{
			if (timeout == -1)
			{
				_cond.wait(l);
			}
			else if (_cond.wait_for(l, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
			{
				return nullptr;
			}
		}
		resource = _unusedUnits.back();
		_usedUnits[resource] = 1;
		_unusedUnits.pop_back();
		return resource;
	}
	void ObjectPool::ReturnBack(void* element) {
		_mutex.lock();
		auto iter = _usedUnits.find(element);
		if (iter != _usedUnits.end())
		{
			_unusedUnits.push_back(element);
			_usedUnits.erase(iter);
			_cond.notify_one();
		}
		_mutex.unlock();
	}
	void* ObjectPool::GetPoolBuffer()
	{
		return _buffer;
	}
	int ObjectPool::GetObjectSize()
	{
		return _elementSize;
	}
	int ObjectPool::GetObjectCount()
	{
		return _arraySize;
	}
}

四、使用示例

1、对象复用,示例:

#include "ObjectPool.h"
class A {
public:
	A() {
		printf("%p\n", this);
	}
};
int main(int argc, char** argv) {
	//初始化对象池,2个对象
	AC::ObjectPool objectPool(new char[sizeof(A) * 2], sizeof(A), 2);
	A* a, * b, * c;
	//申请对象,使用定位new初始化对象
	a = new (objectPool.Applicate())A;
	b = new (objectPool.Applicate())A;
	//归还对象
	a->~A();//返初始化对象
	objectPool.ReturnBack(a);
	c = new (objectPool.Applicate())A;
	b->~A();
	c->~A();
	//使用结束,删除缓存
	delete	objectPool.GetPoolBuffer();
	return 0;
}

输出:
016502E9
016502E8
016502E9

2、简易的线程池,示例:

#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include "ObjectPool.h"
class ThreadInfo {
public:
	std::thread* pThread;
	std::mutex _mutext;
	std::condition_variable _cv;
	std::function<void()> _threadPoc;
};
//线程信息数组,数组长度即线程池的线程数
ThreadInfo _threadArray[3];
//对象池,使用线程信息数组初始化
AC::ObjectPoolGeneric<ThreadInfo>_threadPool(_threadArray, 3);
bool _exitThreadPool = false;
//在线程池中运行方法
void RunInThreadPool(std::function<void()> f) {
	//申请线程
	auto threadInfo = _threadPool.Applicate();
	threadInfo->_threadPoc = f; 
	if (threadInfo->pThread)
		//复用线程
	{
		threadInfo->_cv.notify_one();
	}
	else
		//创建线程
	{
		threadInfo->pThread = new std::thread([=]() {
			while (!_exitThreadPool)
			{
				printf("thread %d run\n", threadInfo->pThread->get_id());
				if (threadInfo->_threadPoc)
				{	//执行线程操作
					threadInfo->_threadPoc();
				}
				printf("thread %d stop\n", threadInfo->pThread->get_id());
				//归还线程
				_threadPool.ReturnBack(threadInfo);
				std::unique_lock<std::mutex>lck(threadInfo->_mutext);
				threadInfo->_cv.wait(lck);
			}
		});
	}
}
int main(int argc, char** argv) {
	while(true)
	{   //在线程池中运行方法
		RunInThreadPool([]() {		
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));	
		});
	}
    return 0;
}

输出:
thread 69664 run
thread 57540 run
thread 56876 run
thread 69664 stop
thread 69664 run
thread 57540 stop
thread 56876 stop
thread 57540 run
thread 56876 run
thread 69664 stop
thread 69664 run
thread 56876 stop
thread 57540 stop
thread 56876 run
thread 57540 run
thread 69664 stop

总结

以上就是今天要讲的内容,本文介绍了对象池的设计与实现以及使用,其使用场景其实不算多,因为很多需要对象复用的场景通常以及有底层实现了,比如线程池数据库的连接池等,所以本文讲的内容只能适用于少数的场景,比如waveOut播放音频时是可以使用对象池实现 的。但总得来说,对象池还是有用的,所以将其写成博客用于记录曾经用过的技术。

到此这篇关于C++ 实现对象池的具体方法的文章就介绍到这了,更多相关C++ 对象池内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://blog.csdn.net/u013113678/article/details/122314080

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 基于C++中常见编译错误的总结详解

    本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
  • c++优先队列(priority_queue)用法详解

    这篇文章主要介绍了c++优先队列(priority_queue)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25