• thinking in object pool


    1.背景 

     对象池为了避免频繁创建耗时或耗资源的大对象,事先在对象池中创建好一定数量的大对象,然后尽量复用对象池中的对象,用户用完大对象之后放回对象池。

    2.问题 

     目前纵观主流语言的实现方式无外乎3个步骤:

    1. 初始创建一定数量的对象池(也允许从外面添加对象)。
    2. 从对象池中取对象来使用。
    3. 用完之后返回对象池。

    一般情况下这样是OK的,可能存在的问题是在第三步,有两个问题:

    1. 不方便,每次都需要显式回收对象。
    2. 忘记将对象放回对象池,造成资源浪费。

    3.改进动机 

     解决显式回收的问题,实现自动回收,省心省力。

    4.技术内幕

    借助c++智能指针,因为智能指针可以自定义删除器,在智能指针释放的时候会调用删除器,在删除器中我们将用完的对象重新放回对象池。思路比较简单,但实现的时候需要考虑两个问题:

    1. 什么时候定义删除器?
    2. 用shared_ptr还是unique_ptr? 

    4.1什么时候定义删除器

      自定义删除器只做一件事,就是将对象重新放入对象池。如果对象池初始化的时候就自定义删除器的话,删除器中的逻辑是将对象放回对象池,放回的时候无法再定义一个这样的删除器,所以这种做法行不通。
    需要注意,回收的对象只能是默认删除器的。除了前述原因之外,另外一个原因是对象池释放的时候需要释放所有的智能指针,释放的时候如果存在自定义删除器将会导致对象无法删除。
    只有在get的时候定义删除器才行,但是初始创建或加入的智能指针是默认删除器,所以我们需要把智能指针的默认删除器改为自定义删除器。 

    4.2用shared_ptr还是unique_ptr

      因为我们需要把智能指针的默认删除器改为自定义删除器,用shared_ptr会很不方便,因为你无法直接将shared_ptr的删除器修改为自定义删除器,虽然你可以通过重新创建一个新对象,把原对象拷贝过来的做法来实现,
    但是这样做效率比较低。而unique_ptr由于是独占语义,提供了一种简便的方法方法可以实现修改删除器,所以用unique_ptr是最适合的。 

    4.3实现源码

    #pragma once
    #include <memory>
    #include <vector>
    #include <functional>
    
    template <class T>
    class SimpleObjectPool
    {
    public:
        using DeleterType = std::function<void(T*)>;
    
        void add(std::unique_ptr<T> t)
        {
            pool_.push_back(std::move(t));
        }
    
        std::unique_ptr<T, DeleterType> get()
        {
            if (pool_.empty())
            {
                throw std::logic_error("no more object");
            }
    
            //every time add custom deleter for default unique_ptr
            std::unique_ptr<T, DeleterType> ptr(pool_.back().release(), [this](T* t)
            {
                pool_.push_back(std::unique_ptr<T>(t));
            });
    
            pool_.pop_back();
            return std::move(ptr);
        }
    
        bool empty() const
        {
            return pool_.empty();
        }
    
        size_t size() const
        {
            return pool_.size();
        }
    
    private:
        std::vector<std::unique_ptr<T>> pool_;
    };
    //测试代码:
    void test_object_pool() { SimpleObjectPool<A> p; p.add(std::unique_ptr<A>(new A())); p.add(std::unique_ptr<A>(new A())); { auto t = p.get(); p.get(); } { p.get(); p.get(); } std::cout << p.size() << std::endl; }

     如果你坚持用shared_ptr,那么回收的时候你需要这样写:

        std::shared_ptr<T> get()
        {
            if (pool_.empty())
            {
                throw std::logic_error("no more object");
            }
    
            std::shared_ptr<T> ptr = pool_.back();
            auto p = std::shared_ptr<T>(new T(*ptr.get()), [this](T* t) 
            {
                pool_.push_back(std::shared_ptr<T>(t));
            });
    
            //std::unique_ptr<T, DeleterType> ptr(pool_.back().release(), [this](T* t)
            //{
            //    pool_.push_back(std::unique_ptr<T>(t));
            //});
    
            pool_.pop_back();
            return p;
        }    

     这种方式需要每次都创建一个新对象,并且拷贝原来的对象,是一种比较低效的做法。 

    5.总结

      凡是需要自动回收的场景下都可以使用这种方式:在获取对象的时候将默认删除器改为自定义删除器,确保它可以回收。注意,回收的智能指针使用的是默认删除器,可以确保对象池释放时能正常释放对象。同时也将获取对象和释放对象时,对象的控制权完全分离。
      其他的一些应用场景:多例模式,无需手动释放,自动回收。 

  • 相关阅读:
    JS阻止鼠标滚动
    仿淘宝订单列表下标指针
    自己动手模拟百分百<select>下拉列表
    专门用来存地址
    JS手动触发事件,转载
    刷新页面让显示区域回到顶部
    解决表格边框问题
    读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
    读书笔记 effective c++ Item 42 理解typename的两种涵义
    读书笔记 effective c++ Item 41 理解隐式接口和编译期多态
  • 原文地址:https://www.cnblogs.com/qicosmos/p/4995248.html
Copyright © 2020-2023  润新知