首页 > 编程语言 >C++ 对象池

C++ 对象池

时间:2024-05-15 22:19:44浏览次数:23  
标签:std obj 对象 C++ cleanup shared ptr

对象池

概念

对象池模式(Object Pool Pattern),是创建型设计模式的一种,将对象预先创建并初始化后放入对象池中,对象提供者就能利用已有的对象来处理请求,减少频繁创建对象所占用的内存空间和初始化时间。

对象池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁。对象池是一个特殊的工厂对象,对象池模式就是单例模式加享元模式。

享元模式(Flyweight Pattern)是一种结构型设计模式,它用于减少创建对象的数量,从而降低内存的使用。享元模式通过共享尽可能多的相似对象来实现这一点。

适用场景

对象池模式主要适用于以下应用场景。

  1. 资源受限的场景。比如,不需要可伸缩性的环境(CPU\内存等物理资源有限),CPU性能不够强劲,内存比较紧张,垃圾收集,内存抖动会造成比较大的影响,需要提高内存管理效率, 响应性比吞吐量更为重要。
  2. 在内存中数量受限的对象。
  3. 创建成本高的对象,可以考虑池化。
  4. 在需要快速响应的系统中,对象池可以减少等待对象创建的时间,从而提高系统的响应性。

常见的使用对象池的场景有在使用Socket时的各种连接池、线程池、数据库连接池等。
线程池也算特殊的对象池

实现方式

对象池的使用:

  1. 从对象池中获取对象,如果没有对象,则创建一个,并返回
  2. 使用对象
  3. 使用完成对象后,将对象还回对象池
    image

实现对象池时注意的点:

  1. 对象的自动回收
  2. 线程安全
  3. 对象池重复利用时进行reset,保持对象的一致性

对象池的实现:

  1. 实现使用shared_ptr或者unique_ptr。
  2. 实现采取new和delete,需要自己去检查什么对象需要回收

无法直接将shared_ptr的删除器修改为自定义删除器,所以在使用shared_ptr时,需要一开始就定义回收函数,并把创建都委托给对象池
使用unique_ptr时,可以很方便的修改删除器

缺点和可以优化的点

对象池的缺点:

  1. 对象池在使用的时候,如果不加以控制,那么会增加内存消耗
    比如仅在某个时间段突然需要大量对象,其他时间只用少量对象
    此时对象池不够,需要添加更多的对象。而当增加完对象后,大量对象长时间存在内存里没有被使用,就导致了内存消耗。

未来优化:

  1. 可以在对象池初始化时传入工厂对象,用工厂来进行创建,而不是简单的new

示例代码

代码中需要注意使用对象池时,需要对象创建时没有入参,必须实现清理函数cleanup,以保证放入对象池中对象是干净可以复用的。
包含测试代码

#include <iostream>
#include <vector>
#include <functional>
#include <mutex>
#include <memory>
#include <atomic>
#include <thread>

template <typename T, typename = void>
struct has_cleanup : std::false_type {};

template <typename T>
struct has_cleanup<T, std::void_t<decltype(std::declval<T>().cleanup())>> : std::true_type {};

// 对象池
// 可以结合单例模式使用
template<typename T>
class ObjectPool
{
private:
    int maxsize_;
    int minsize_;
    int count_;
    std::mutex mutex_;
    std::vector<std::shared_ptr<T>> obj_list_;
    std::function<void(T* obj_ptr)> obj_destroy_func_;
    std::atomic_bool is_destructed_;
    
public:
    ObjectPool(int minSize, int maxSize)
    {
        static_assert(has_cleanup<T>::value, "T must have a cleanup() function");
        maxsize_ = maxSize;
        minsize_ = minSize;
        obj_list_.reserve(maxSize);

        obj_destroy_func_ = [&](T* obj_ptr)
        {
            if(is_destructed_ == true)
            {
                obj_ptr->cleanup();
                delete obj_ptr;
            }
            else
            {
                // 加入回收池
                obj_ptr->cleanup();
                std::lock_guard<std::mutex> lock(mutex_);
                obj_list_.push_back(std::shared_ptr<T>(obj_ptr, obj_destroy_func_));
            }
        }; 
        for(int i = 0; i < minsize_; i++)
        {
            obj_list_.emplace_back(new T(), obj_destroy_func_);
        }
        count_ = minsize_;
    }
    ~ObjectPool()
    {
        is_destructed_ = true;
    }
    
    std::shared_ptr<T> GetObject()
    {
        std::shared_ptr<T> result;
        std::lock_guard<std::mutex> lock(mutex_);
        if(obj_list_.empty())
        {
            if(count_ < maxsize_)
            {
                count_++;
                result = std::shared_ptr<T>(new T(), obj_destroy_func_);
            }
            else
            {
                // throw std::runtime_error("ObjectPool is full");
                std::cout << "ObjectPool is full" << std::endl;
                return nullptr;
            }
        }
        else
        {
            result = obj_list_.back();
            obj_list_.pop_back();
        }
        return result;
    }
};


class test
{
public:
    test(): num_{999} {}
    void init(int num)
    {
        num_ = num;
    }
    int num_;
    void cleanup()
    {
        std::cout << "cleanup: " << num_ << std::endl;
    }
};

int main()
{
    
    {
        ObjectPool<test> pool(5, 10);
        for(int i = 0; i < 3; i++)
        {
            std::shared_ptr<test> obj_ptr = pool.GetObject();
            if(obj_ptr != nullptr)
            {
                obj_ptr->init(i);
            }
            else
            {
                std::cout << "GetObject failed" << std::endl;
            }
        }
    }
    
    
    // std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "------------------------------------" << std::endl;
    return 0;
}


参考链接
https://zhuanlan.zhihu.com/p/437751056
https://blog.csdn.net/CJF_iceKing/article/details/119982775
https://www.cnblogs.com/qicosmos/p/4995248.html

标签:std,obj,对象,C++,cleanup,shared,ptr
From: https://www.cnblogs.com/hy227/p/18194802

相关文章

  • C++基础篇
    输入输出流iostream向流写入数据<<运算符<<运算符接受两个运算对象,此运算符将给定的值写到给定的ostream对象中:左侧:运算对象为ostream对象,如cout、cerr、clog右侧:运算对象是要打印的值输出结果:写入给定值的那个ostream对象,即此运算符返回其左侧的运算对象。表达式等价于:(std......
  • msvc 获取c++类内存布局 /d1 reportAllClassLayout
     visualstudio配置获取所有类内存布局/d1reportAllClassLayout或者指定类/d1reportSingleClassLayoutXXXclass  编译时输出:     ps:https://www.openrce.org/articles/full_view/23   【原文地址】https://blog.csdn.net/qq_29542611/article......
  • javascript 将变量值作为对象属性 获取对象对应的值
      test(){letform={bar_rule_txt:'{spu}-{master_attr_value}-{slave_attr_alias}',bar_rule_result:'',spu:'JPK1575G',master_attr_value:'黑色',master......
  • linux下使用c++模拟下载进度
    #include<iostream>#include<iomanip>#include<chrono>#include<thread>voidshowProgressBar(doubleprogress){constintbarWidth=70;std::cout<<"\r[";intpos=static_cast<int>(barWid......
  • 去除两个JSON对象集合中的重复数据
    在jQuery中,要去除两个JSON对象集合中的重复数据,你通常需要比较这两个集合中对象的特定属性来决定是否重复。以下是一个基本的方法,假设我们根据每个对象的id属性来判断是否重复,并且我们将结果保存到第一个集合中,去除掉与第二个集合中重复的项://假设这是你的两个JSON对象集合var......
  • C++封装dll(__cdecl和__stdcall)
    【1】使用__stdcall还需要添加def文件编译,使用工具DEPENDS.EXE打开dll文件成功。【2】使用__cdecl直接编译即可,不需要导入def文件......
  • 使用c#强大的表达式树实现对象的深克隆之解决循环引用的问题
    在上一期博客里,我们提到使用使用c#强大的表达式树实现对象的深克隆,文章地址:https://www.cnblogs.com/gmmy/p/18186750。但是文章里没有解决如何实现循环引用的问题。循环引用在C#中,循环引用通常发生在两个或更多的对象相互持有对方的引用,从而形成一个闭环。这种情况在使用面向对......
  • 使用c#强大的表达式树实现对象的深克隆
    使用c#强大的表达式树实现对象的深克隆 一、表达式树的基本概念表达式树是一个以树状结构表示的表达式,其中每个节点都代表表达式的一部分。例如,一个算术表达式 a+b 可以被表示为一个树,其中根节点是加法运算符,它的两个子节点分别是 a 和 b。在LINQ(语言集成查询)中,表达......
  • 使用c#强大的表达式树实现对象的深克隆
    一、表达式树的基本概念表达式树是一个以树状结构表示的表达式,其中每个节点都代表表达式的一部分。例如,一个算术表达式a+b可以被表示为一个树,其中根节点是加法运算符,它的两个子节点分别是a和b。在LINQ(语言集成查询)中,表达式树使得能够将C#中的查询转换成其他形式的查询......
  • C++继承
    继承通过继承机制可以实现对代码的拓展以及重用,而不用通过复制粘贴的方式来实现重用继承语法:ClassB:publicA{...}; public是公用继承用的最多,B是子类(派生类),A是父类(基类)子类可以访问从父类被public修饰的成员变量和函数,以及一些新增加的函数和变量,子类不能直接访问父......