使用C++11实现对象池

这里写自定义目录标题

  • 使用C++11实现对象池
    • 基本功能介绍
    • 相关代码基本介绍
      • ObjectPool
      • ObjWrapper
      • 模板参数T
      • template
    • 代码
      • ObjectPool.h
      • 测试代码
      • 测试结果
    • 最后说明

使用C++11实现对象池

基本功能介绍

本文使用现代C++语法实现了一个简单的对象池,主要提供了以下功能:

  • 支持创建指定大小的对象池
  • 支持对象池中的对象通过可变参数的方式进行创建,提高了对象池的通用性
  • 使用模板,可以创建不同类型的对象池对象
  • 支持对对象池中的对象进行使用率统计
  • 支持获取对象池中不在使用的对象
  • 支持对象池的扩容,暂时只提供外界调用者进行扩容的行为,不主动扩容

相关代码基本介绍

ObjectPool

对象池对象。

ObjWrapper

对对象池中的对象进行包装,提供m_bUsing属性来判断当前池中的对象是处于在使用状态还是不在使用状态。

模板参数T

对象池可以创建的对象的类型。

template

对象池中对象的构造过程通过外界传入的参数来动态选择相应构造函数对池中对象进行构造。

代码

其中具体的解释我都以注释的方式在代码中进行了说明。

ObjectPool.h

#pragma once

#include
#include

/*
* 暂时使用vector作为对象池对象的存储容器
*/
template<typename T>
class ObjectPool
{
public:
	/*
	* 提供无参构造,可以在不传递任何参数时创建对象池对象
	*/
	ObjectPool() {}

	/*
	* 提供带参数的构造,并指定创建对象池时所创建对象的构造函数的参数列表
	*/
	template<typename... Args>
	ObjectPool(int num, Args... args)
	{
		CreatePoolObjs(num, std::forward<Args>(args)...);
	}

	/*
	* 析构函数中将对象池的对象进行清除
	* 这样外面代码就不需要显式调用ClearObjs来清除,减少出错机会
	* 
	* 随便举一个出错机会
	* 如果外面代码是通过RAII的方式来管理对象池对象,外面代码必须主动调用ClearObjs来清除
	* 见main.cpp tt4()函数
	* auto pool = std::make_shared>(10, "张三", 20);
	* auto pool = std::shared_ptr>(new ObjectPool(10, "张三", 20));
	*/
	~ObjectPool() { ClearObjs(); }

	class ObjWrapper
	{
	public:
		/*
		* 对原始的T对象进行包装,提供一些额外的公用属性,比如对象是否在被使用(m_bUsed)
		* 
		* 使用外界传进来参数列表来动态创建T对象
		* 要求T类型有该参数列表的构造函数
		*/
		template<typename... Args>
		ObjWrapper(Args... args)
		{
			printf("create obj: %p\n", this);
			//obj = new T(std::forward(args)...);
			m_obj = std::make_shared<T>(std::forward<Args>(args)...);
			m_bUsing = false; // 创建时状态为当前不在使用
		}

		~ObjWrapper()
		{
			printf("destroy obj: %p\n", this);
			//delete obj; //如果成员变量是T* obj;类型,且构造函数有new的行为,需要在析构函数中手动回收空间	
		}

		void SetUsing(bool bUsed)
		{
			m_bUsing = bUsed;
		}

		bool IsUsing()
		{
			return m_bUsing;
		}

		/*
		* 返回被包装的原始对象
		*/
		/*T* GetRawObj()
		{
			return obj;
		}*/

		std::shared_ptr<T> GetObj()
		{
			return m_obj;
		}
	private:
		//T* obj;					// 直接是原始对象,需要ObjWrapper中实现对原始对象的内存释放
		std::shared_ptr<T> m_obj;	// 原始对象的RAII,自动内存管理,不需要自己管理原始对象的内存释放
		bool m_bUsing;
	};

	/*
	* 为对象池一次性创建num个内存对象
	* 支持可变参数的构造
	*/
	template<typename... Args>
	void CreatePoolObjs(int num, Args... args)
	{
		for (int i=0;i<num;i++)
		{
			auto obj = std::make_shared<ObjWrapper>(std::forward<Args>(args)...);//std::move(args)...
			m_objs.push_back(std::move(obj));// 使用移动语义,代替m_objs.push_back(obj);
		}		
	}

	/*
	* 往对象池中增加一个对象,此行为会改变对象池的大小
	* 支持可变参数的构造
	* 当对象池中对象都处于被使用的状态时可以用此函数对对象池进行相应扩容
	*/
	template<typename... Args>
	void PushObj(Args... args)
	{
		auto obj = std::make_shared<ObjWrapper>(std::forward<Args>(args)...);
		m_objs.push_back(std::move(obj));
	}

	/*
	* 获取对象池中的一个未使用的对象
	* 这里暂时使用简单的O(N)行为来获取一个当前未使用的对象,暂时没做扩容处理(TODO:使用者可以根据自己需要进行相应扩容处理)
	*/
	std::shared_ptr<ObjWrapper> GetUnUsingOne()
	{
		for (auto obj : m_objs)
		{
			if (!obj->IsUsing())
			{
				return obj;
			}
		}
		return nullptr;
	}

	/*
	* 统计对象池的使用率
	*/
	double GetUsingRatio()
	{
		auto total = GetSize();
		if (total == 0)
		{
			return 0.0;
		}

		int useCount = 0;
		for (size_t i = 0; i < total; i++)
		{
			if (m_objs[i] && m_objs[i]->IsUsing())
			{
				useCount++;
			}
		}
		return (double)useCount / (double)total;
	}

	/*
	* 复用对象池中的数据
	* 只设置use标识,不更改数据,后续可根据实际业务来更改数据内容
	*/
	void ReUsePool()
	{
		for (size_t i = 0; i < GetSize(); i++)
		{
			if (m_objs[i] != nullptr)
			{
				m_objs[i]->SetUsing(false);
			}		
		}
	}

	/*
	* 返回对象池中对象个数
	*/
	size_t GetSize()
	{
		return m_objs.size();
	}

private:
	/*
	* 清空对象池中的对象
	* 设为私有,不允许外部显式调用来清空对象池中的对象
	*/
	void ClearObjs()
	{
		m_objs.clear();
	}
private:
	std::vector<std::shared_ptr<ObjWrapper>> m_objs;
};

测试代码

#include 
#include 
#include 
#include"ObjectPool.h"

using namespace std;

class Teacher {
public:
	/*
	* 提供无参构造函数
	*/
	Teacher() :m_name(__func__), m_age(0) {}
	/*
	* 提供只设置年龄的构造函数
	*/
	Teacher(int age) : m_name(__func__), m_age(age)
	{
	}
	/*
	* 提供初始化姓名和年龄的构造函数
	*/
	Teacher(const string& name, int age) : m_name(name), m_age(age)
	{
	}
	/*
	* 析构函数
	*/
	~Teacher() {
	}

	/*
	* 拷贝构造函数
	*/
	Teacher(const Teacher& a){
		m_name = a.m_name;
		m_age = a.m_age;
	}

	/*
	* 等号运算符的重载
	*/
	Teacher& operator=(const Teacher& a) {
		m_name = a.m_name;
		m_age = a.m_age;
	}

	/*
	* 获取名字
	*/
	string get_name() {
		return m_name;
	}

	/*
	* 获取年龄
	*/
	int get_age()
	{
		return m_age;
	}
private:
	std::string m_name;
	int m_age;
};

void tt3()
{
	ObjectPool<Teacher> pool;

	pool.CreatePoolObjs(10,24);		// 创建一个初始大小为10的对象池
	pool.PushObj(60);					// 往对象池里面新增一个对象,支持可变参数
	pool.PushObj("zhangsan",30);	// 往对象池里面新增一个对象,支持可变参数

	for (size_t i = 0; i < pool.GetSize(); i++)
	{
		auto obj = pool.GetUnUsingOne();
		if (obj != nullptr && obj->GetObj()!=nullptr)
		{
			//printf("%p's name=%s, age = % d\n", obj.get(), obj->GetRawObj()->get_name().c_str(), obj->GetRawObj()->get_age());
			printf("%p's name=%s, age = % d\n", obj.get(), obj->GetObj()->get_name().c_str(), obj->GetObj()->get_age());
			obj->SetUsing(true);
		}	
	}

	// 打印查看对象池的使用率
	printf("object pool use ratio: %.2lf%%\n", pool.GetUsingRatio()*100);
	// 重用对象池对象,设置池中所有对象使用状态为未使用
	pool.ReUsePool(); 

	// 重用对象池对象后打印查看对象池的使用率
	printf("\n\nAfter ReUsePool, object pool use ratio: %.2lf%%\n\n", pool.GetUsingRatio()*100);

	// 检查是否真的可以重用了,打印可重用对象的信息
	for (size_t i = 0; i < pool.GetSize(); i++)
	{
		auto obj = pool.GetUnUsingOne();
		if (obj != nullptr && obj->GetObj() != nullptr)
		{
			printf("%p's name=%s, age = % d\n", obj.get(), obj->GetObj()->get_name().c_str(), obj->GetObj()->get_age());
			obj->SetUsing(true);
		}
	}
}

void tt4()
{
	// 创建对象个数为10的对象池,并对每个对象初始化为["张三",20],这里调用了Teacher带两个参数的构造函数对
	// 对象池中的对象进行初始化
	// ObjectPool pool(10,"张三",20); 普通栈变量

	// 以下两种方式都可创建对象池对象的RAII对象
	auto pool = std::make_shared<ObjectPool<Teacher>>(10, "张三", 20);//RAII栈变量(管理堆区内存)
	//auto pool = std::shared_ptr>(new ObjectPool(10, "张三", 20)); 

	// 对每一个不在使用的对象进行操作,用完后设置状态为正在使用中
	// 以打印这个行为来模拟使用对象池中的对象这个行为
	for (size_t i = 0; i < pool->GetSize() ; i++)
	{
		auto obj = pool->GetUnUsingOne();
		if (obj != nullptr)
		{
			//printf("%p's name=%s, age = % d\n", obj.get(), obj->GetRawObj()->get_name().c_str(), obj->GetRawObj()->get_age());
			printf("%p's name=%s, age = % d\n", obj.get(), obj->GetObj()->get_name().c_str(), obj->GetObj()->get_age());
			obj->SetUsing(true);
		}
	}
}


int main()
{
	tt3();
	//tt4();
	return 0;
}

测试结果

以下展示其中一种测试结果,因为每次执行分配的内存单元是不一样的。

create obj: 0098C6E4
create obj: 0098C84C
create obj: 0098C414
create obj: 0098C534
create obj: 0098CA8C
create obj: 0098C45C
create obj: 0098C8DC
create obj: 0098C774
create obj: 0098C57C
create obj: 0098C924
create obj: 0098CAD4
create obj: 0098C4EC
0098C6E4's name=Teacher, age =  24
0098C84C's name=Teacher, age =  24
0098C414's name=Teacher, age =  24
0098C534's name=Teacher, age =  24
0098CA8C's name=Teacher, age =  24
0098C45C's name=Teacher, age =  24
0098C8DC's name=Teacher, age =  24
0098C774's name=Teacher, age =  24
0098C57C's name=Teacher, age =  24
0098C924's name=Teacher, age =  24
0098CAD4's name=Teacher, age =  60
0098C4EC's name=zhangsan, age =  30
object pool use ratio: 100.00%


After ReUsePool, object pool use ratio: 0.00%

0098C6E4's name=Teacher, age =  24
0098C84C's name=Teacher, age =  24
0098C414's name=Teacher, age =  24
0098C534's name=Teacher, age =  24
0098CA8C's name=Teacher, age =  24
0098C45C's name=Teacher, age =  24
0098C8DC's name=Teacher, age =  24
0098C774's name=Teacher, age =  24
0098C57C's name=Teacher, age =  24
0098C924's name=Teacher, age =  24
0098CAD4's name=Teacher, age =  60
0098C4EC's name=zhangsan, age =  30
destroy obj: 0098C6E4
destroy obj: 0098C84C
destroy obj: 0098C414
destroy obj: 0098C534
destroy obj: 0098CA8C
destroy obj: 0098C45C
destroy obj: 0098C8DC
destroy obj: 0098C774
destroy obj: 0098C57C
destroy obj: 0098C924
destroy obj: 0098CAD4
destroy obj: 0098C4EC

F:\HappyCoding\obj_pool\Debug\obj_pool.exe (process 23724) exited with code 0.
Press any key to close this window . . .

最后说明

欢迎大佬进行指导。

你可能感兴趣的:(C++或Windows,c++,开发语言)