boost:pool 青旅半醒 2022-09-02 00:56 163阅读 0赞 > * 内存池预先分配了一块大的内存空间,然后就可以在其中使用某种算法实现快速的自定制内存分配 > * `boost.pool`库基于简单分隔存储思想实现了一个快速、紧凑的内存池库,不仅能够管理大量的对象,还可以被用作STL的内存分派器。某种程度上讲,它近似与一个小型的垃圾回收机制,在需要大量的分配/释放小对象时很有效率,而且完全不需要考虑`delete` > * pool库包含四个组成补充:最简单的`pool`、分配类实例的`object_pool`、单件内存池`singleton_pool`和可用于标准库的`pool_alloc` # pool # `pool`是最简单也最容易使用的内存池类,可以返回一个POD内存指针。它位于名字空间boost,需要包含`<boost/pool/pool.hpp>`,即: #include <boost/pool/pool.hpp> using namespace boost; ## 类摘要 ## ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70] ## 操作函数 ## `pool`的模板类型参数`UserAllocator`是一个用户定义的内存分派器,它实现了特定的内存分配算法,通常可以直接用默认的`default_user_allocator_new_delete`,它内部使用`new[]`和`delete[]`分配内存。 pool的构造函数接受一个`size_type`类型的整数`requested_size`,指示每次分配内存块的大小(而不是内存池的大小),这个值可以用`get_requested_size()`获得。`pool`会根据需要自动的向系统申请或者归还使用的内存,在析构时,pool将自动释放它所持有的所有内存 成员函数`malloc()`和`ordered_malloc()`的行为很类似C中的全局函数`malloc()`,用`void *`指针返回从内存池中分配的内存块,大小为构造函数中指定的`requested_size`。如果内存分配失败,函数会返回0(即空指针),不会抛出异常。`malloc()`从内存池中任意分配一个内存块 ,而`ordered_malloc()`则会在分配的同时合并空闲块链表。`ordered_malloc()`带参数的形式还可以连续分配n快内存。分配后的内存块可以用`is_from()`函数测试是否是从这个内存池分配出去的。 与`malloc()`对应的一组函数是free(),用来手动释放之前分配的内存块,这些内存块必须是从内存池分配出去的(`is_from(chunk)==true`)。一般情况下内存池会自动管理内存分配,不应该调用free(),除非你认为内存池的空间已经不足,必须释放已经分配的内存。 最后还有两个成员函数:`release_memory()`让内存池释放所有未被分配的内存,但已分配的内存块不受影响;`purge_memory()`则强制释放`pool`持有的所有内存,不管内存块是否被使用。实际上,`pool`的析构函数就是调用的`purge_memory()`。这两个函数一般情况下也不应该由程序员手动调用 ## 用法 ## pool很容易使用,可以像C中的malloc()一样分配内存,然后随意使用。除非由特殊要求,否则不必对分配的内存调用free()释放,pool会很好的管理内存。例如: #include <iostream> #include <boost/pool/pool.hpp> using namespace boost; int main() { pool<> p1(sizeof(int )); //一个可分配int的内存池 int *p = static_cast<int *>(p1.malloc()); // 必须把void*转换成需要的类型 if(p == nullptr){ // 因为pool在分配内存失败时不会抛出异常,所以应该检查p以防止空指针错误,不过这种情况极少见 printf("[error], pool.malloc() failed\n"); return 0; } assert(p1.is_from(p)); p1.free(p); // 归还内存池分配的对象,不过一般不应该手动调用,pool会自动管理 for(int i = 0; i < 100; ++i){ p1.ordered_malloc(10); // 连续分配大量的内存 } return 0; } // 内存池对象析构,所有分配的内存在这里都会被自动释放 pool很容易使用,不过需要注意一点:**它只能作为普通数据类型如int、double等的内存池,不能应用于复杂的类和对象,因为它只分配内存,不调用构造函数**。如果有这种需求,我们应使用`object_pool` # object\_pool # object\_pool是用于类实例(对象)的内存池,它的功能与pool类似,但会在析构时对所有已经分配的内存块调用析构函数,从而正确的释放资源。 `object_pool`位于名字空间boost,需要包含头文件`<boost/pool/object_pool.hpp>`: #include <boost/pool/object_pool.hpp> using namespace boost; ## 类摘要 ## object\_pool的类摘要如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1] ## 操作函数 ## `object_pool`是pool的子类,但它使用的是保护继承,因此不能使用pool的接口,但基本操作还是很相似的。 object\_pool的模板类型参数T指定了object\_pool要分配的元素类型,要求其析构函数不能抛出异常。一旦在模板中指定了类型,object\_pool实例就不能再用于分配其他类型的对象。 malloc()和free()函数分别分配和释放一块类型为T\*的内存块,同样,可以用`is_from()`来测试内存块的归属,只有是本内存池分配的内存才能被`free()`释放。但它们被调用时并不调用类的构造函数和析构函数。也就是说,操作的是一块原始内存块,里面的值是未定义的,因此我们应当尽量少的使用`malloc`和`free`。 这些函数都不会抛出异常,如果内存分配失败,将返回0. ## 用法 ## object\_pool的用法也很简单,我们既可以像pool那样分配原始内存块,也可以使用construct()来直接在内存池中创建对象。当然,后一种使用方法是最方便的,也是推荐的。 #include <iostream> #include <boost/pool/object_pool.hpp> using namespace boost; struct demo_class{ public: int a, b, c; demo_class(int x = 1, int y = 2, int z = 3) : a(x), b(y), c(z){ } }; int test() { object_pool<demo_class> p1; // 对象内存池 /* auto p = p1.malloc(); //不推荐使用:分配一个原始内存块 assert(p1.is_from(p)); // p指向的内存未经初始化 assert(p->a != 1 || p->b != 2 || p->c != 3); */ p = p1.construct(7, 8, 9); // 构造一个对象,可以传递参数 assert(p->a == 7); object_pool<std::string> pls; // 定义一个分配string对象的内存池 for(int i = 0; i < 10; i++){ // ;连续分配大量的string对象 std::string *ps = pls.construct("Hello object_pool"); std::cout << *ps << std::endl; } return 0; } // 所有创建的对象在这里都被正确析构,释放了 int main(){ test(); return 0; } ## 更多的构造函数 ## 默认情况下,在使用object\_pool的construct()的时候我们只能最多使用三个参数来创建对象。大多数情况下都是足够的,但有的时候我们可能会定义三个以上参数的构造函数,此时construct()的默认重载形式就不能用了。 很遗憾,construct()并没有及时跟进C++标准使用可变参数模板支持任意数量的参数构造。 解决方案:定义一个辅助函数: #include <iostream> #include <boost/pool/object_pool.hpp> using namespace boost; template<typename P, typename ... Args> inline typename P::element_type * construct(P &p, Args&& ... args){ typename P::element_type * mem = p.malloc(); assert(mem != 0); new (mem) typename P::element_type( std::forward<Args>(args)... ); return mem; } struct demo_class{ demo_class(int, int, int, int){ printf("deme_class ctor\n"); } ~demo_class(){ printf("deme_class dtor"); } }; void test(){ object_pool<demo_class> p1; auto d = construct(p1, 1, 2, 3, 4); } int main() { test(); return 0; } 自由函数construct()接收任意多个参数,第一个是object\_pool对象,其后是创建对象所需参数,要创建的对象类型可以使用object\_pool的内部类型定义element\_type来获得。函数中实现调用malloc()分配一块内存,然后使用**布局new**来创建对象 # singleton\_pool # `singleton_pool`与`pool`的接口完全一致,可以分配简单数据类型(POD)的内存指针,但它是一个单件。 singleton\_pool位于名字空间boost,需要包含头文件`<boost/pool/singleton_pool.hpp>` #include <boost/pool/singleton_pool.hpp> using namespace boost; singleton\_pool默认使用boost.thread库提供线程安全保证,所以需要连接boost\_thread库,如果不使用多线程,那么可以在头文件前定义宏BOOST\_POOL\_NO\_MT ## 类摘要 ## ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2] ## 用法 ## singleton\_pool主要有两个模板类型参数(其余的可以使用默认值)。第一个Tag仅仅是用于标记不同的单件,可以是空类,甚至只是声明。第二个参数`RequestedSize`等同于pool构造函数中的整数request\_size,指示pool分配内存块的大小。 singleton\_pool的接口与pool完全一致,但成员函数均为静态,所以不需要声明singleton\_pool的实例(当然单例模式也无法创建实例),直接用域操作符`::`来调用静态成员函数。因为singleton\_pool是单件,所以它的声明周期和整个程序同样长,除非手动调用release\_memory()或者purge\_memory(),否则singleton\_pool不会自动释放所占用的内存。除了这两点,singleton\_pool的用法与pool完全相同。 #include <iostream> #include <boost/pool/singleton_pool.hpp> using namespace boost; struct pool_tag{ }; //仅仅用于标记的空类 typedef singleton_pool<pool_tag, sizeof(int)> sp1; // 内存池定义 int main() { int *p = (int *) sp1::malloc(); // 分配一个整数内存块 assert(sp1::is_from(p)); // sp1::release_memory(); // 释放所有未被分配的内存 return 0; } // sp1的内存直到程序结束才完全释放,而不是退出作用域 singleton\_pool在使用时最好使用typedef来简化名称,否则会使得类型名过于冗长,比如: typedef singleton_pool<pool_tag, sizeof(int)> sp1; // 内存池定义 用于标记的类pool\_tag可以再进行简化,直接在模板参数列表中声明tag类,这样可以在一条语句中完成对singleton\_pool的类型定义,比如: typedef singleton_pool<struct pool_tag, sizeof(int)> sp1; // 内存池定义 # pool\_alloc # pool\_alloc提供了两个可以用于标准容器模板参数的内存分配器,分别是pool\_alloc和fast\_pool\_allocator,它的行为和之前的内存池类有一点不同----当内存分配失败是会抛出异常std::bad\_alloc。它位于名字空间boost,需要包含头文件 #include <iostream> #include <boost/pool/pool_alloc.hpp> using namespace boost; int main() { std::vector<int, pool_allocator<int>> v; // pool_allocator代替默认的内存分配器 v.push_back(10); printf("%zu\n", v.size()); return 0; } # 其他 # pool库还提供一个底层的实现类simple\_segregated\_storage,它实现了简单分隔存储的管理机制,是pool库其他类的基础。它不适合大多数库用户,但可以作为自行实现内存池类的一个起点。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]: /images/20220829/f8b031a62a6c427090bea52013175a3a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1]: /images/20220829/7f44a70939454f80a964e2a7bb7ef330.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2]: /images/20220829/23a0c3b3e49f4916b84f8ea08146df98.png
还没有评论,来说两句吧...