C++对象池设计与实现

目录

一、对象池简介

1.1 池化技术

1.2 什么是对象池

1.3 对象池分配策略

二、C++ new和delete运算符重载

三、实现一个对象池框架

3.1 策略接口

四、实现几种对象池的分配策略

4.1 数组策略

4.2 堆策略

​编辑

4.3 栈策略

4.4 区块策略


一、对象池简介

1.1 池化技术

线程池、连接池、内存池

池化技术共同点

提前创建资源,以备不时之需时重复利用,极致的提升性能。

由于在实际应用里分配内存、创建进程、线程,都会涉及到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池等技术来提升程序的性能。

1.2 什么是对象池

对象池简介

对象池的实现和内存池的实现原理很像:都是-开始申请大内存空间, 然后把大内存分配成小内存空间,当需要使用的时候直接分配使用,不再向系统申请内存空间,也不直接释放内存空间。使用完之后都是放回池子里。

注意

对象池其实就是一种特殊的内存池,仅分配固定大小的内存。

1.3 对象池分配策略

● 基于数组的策略

● 基于堆的策略

● 基于栈的策略

● 基于区块的策略

课程里我们会实现以上几种对象池的分配策略,从最简单的数组策略到比较复杂的区块策略。每种策略都有各自的特点和适用场景。

二、C++ new和delete运算符重载

class A
{
public:
    void * operator new(size_t n)
    {
        std::cout << "A new" << std::endl; 
        return ::malloc(n);
    }
    void operator delete(void * p)
    {
        std::cout << "A delete" << std::endl;
        ::free(p);
    }
};

三、实现一个对象池框架

3.1 策略接口

template <typename T>
class Allocator {
public:
    virtual T * allocate() = 0;
    virtual void deallocate(T * p) = 0;
};

策略接口函数:

allocate:分配内存

deallocate:回收内存

现在创建和析构对象不需要用new和free了,而是用对象池的创建和析构函数。

// a.h

#pragma once
#include <iostream>
#include "object_pool.h"
#include "malloc_allocator.h"

using namespace huan::object;

class A
{
private:
    typedef ObjectPool<A, MallocAllocator<A>> ObjectPool;
    static ObjectPool pool;
public:
    A()
    {
        std::cout << "A construct" << std::endl;
    }
    ~A()
    {
        std::cout << "A destruct" << std::endl;
    }

    void * operator new(size_t n)
    {
        std:: cout << "A new" << std::endl;
        return pool.allocate(n);
    }

    void operator delete(void * p)
    {
        std::cout << "A delete" << std::endl;
        pool.deallocate(p);
    }
};

A::ObjectPool A::pool;

// object_pool.h

#pragma once

#include <stdexcept>

namespace huan
{
    namespace object
    {
        template <typename T, typename Allocator>
        class ObjectPool
        {
        public:
            ObjectPool() = default;
            ~ObjectPool() = default;

            void * allocate(size_t n)
            {
                if (sizeof(T) != n)
                    throw std::bad_alloc();
                return m_allocator.allocate();
            }

            void deallocate(void * p)
            {
                m_allocator.deallocate(static_cast<T *>(p));
            }
        private:
            Allocator m_allocator;
        };
        
    }
}

// malloc_allocator.h

#pragma once

#include "allocator.h"

namespace huan
{
    namespace object
    {
        template <typename T>
        class MallocAllocator : public Allocator<T>
        {
        public:
            MallocAllocator() = default;
            ~MallocAllocator() = default;

            virtual T * allocate()
            {
                auto p = ::malloc(sizeof(T));
                return reinterpret_cast<T *>(p);
            }

            virtual void deallocate(T * p)
            {
                ::free(p);
            }
        };
    }
}

// allocator.h

#pragma once

namespace huan
{
    namespace object
    {
        template <typename T>
        class Allocator
        {
        public:
            virtual T * allocate() = 0;
            virtual void deallocate(T * p) = 0;
        };
    }
}

四、实现几种对象池的分配策略

4.1 数组策略

#include <src/a.h>

int main()
{
    A * arr[max_size] = { nullptr };

    for (int i = 0; i < max_size; i++)
    {
        A * a = new A();
        arr[i] = a;
    }

    for (int i = 0; i < max_size; i++)
    {
        delete arr[i];
    }

    return 0;
}

#pragma once

#include "allocator.h"

namespace huan
{
    namespace object
    {
        template <typename T, int N>
        class ArrayAllocator : public Allocator<T>
        {
        public:
            ArrayAllocator()
            {
                for (int i = 0; i < N; i++)
                {
                    m_used[i] = false;
                }
            }
            ~ArrayAllocator() = default;

            virtual T * allocate()
            {
                for (int i = 0; i < N; i++)
                {
                    if (!m_used[i])
                    {
                        m_used[i] = true;
                        return reinterpret_cast<T *>(&m_data[sizeof(T) * i]);
                    }
                }

                // 如果没找到
                throw std::bad_alloc();
            }

            virtual void deallocate(T * p)
            {
                auto i = ((unsigned char *)p - m_data) / sizeof(T);
                m_used[i] = false;
            }

        private:
            unsigned char m_data[sizeof(T) * N];
            bool m_used[N];
        };
    }
}

注意:基于数组的时间复杂度高 O(n)

4.2 堆策略

使用大根堆,heap第一个位置总是空闲的。在插入一个对象后,堆会重新把一个没有使用的位置放到第一位

// heap_allocator.h

#include <algorithm>
#include "allocator.h"

namespace huan
{
    namespace object
    {
        template <typename T, int N>
        class HeapAllocator : public Allocator<T>
        {
        public:
            enum State
            {
                FREE = 1,
                USED = 0,
            };

            struct Entry
            {
                State state;    // 状态
                T * p;          // 对象指针

                bool operator < (const Entry & other) const
                {
                    return state < other.state;
                }
            };

            HeapAllocator()
            {
                m_available = N;

                for (int i = 0; i < N; i++)
                {
                    m_entry[i].state = FREE;    // 未使用
                    m_entry[i].p = reinterpret_cast<T *>(&m_data[sizeof(T) * i]);
                }

                // 调用生成大堆的算法
                std::make_heap(m_entry, m_entry + N);
            }
            ~HeapAllocator() = default;

            virtual T * allocate()
            {
                if (m_available <= 0)
                    throw std::bad_alloc();
                Entry e = m_entry[0];
                std::pop_heap(m_entry, m_entry + N);
                m_available--;

                m_entry[m_available].state = USED;
                m_entry[m_available].p = nullptr;
                    
                return e.p;
            }

            virtual void deallocate(T * p)
            {
                if (p == nullptr || m_available >= N)
                    return;
                m_entry[m_available].state = FREE;
                m_entry[m_available].p = reinterpret_cast<T *>(p);

                m_available++;

                std::push_heap(m_entry, m_entry + N);
            }

        private:
            unsigned char m_data[sizeof(T) * N];
            Entry m_entry[N];
            int m_available;
        };
    }
}

时间复杂度 O(log n)

4.3 栈策略

4.4 区块策略

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当学习 C 语言时,通常的学习过程包括以下几个阶段: 1. 理解基础概念:这个阶段通常涉及到了数据类型、控制结构和函数等重要的基础概念。你需要理解各种数据类型(如整数、浮点数、字符、数组和结构体等)以及控制结构(如 if、while 和 for 语句)的工作原理,并且能够编写一些简单的函数。 2. 确认内存管理:C 是一门底层语言,因此需要手动管理内存。在学习 C 的过程中,你需要理解大小与类型对内存占用的影响,并且掌握内存的分配和释放等相关知识。 3. 掌握文件 I/O: C 语言常常用作系统级编程,因此文件 I/O 是一项必备的技能。学习如何读写文件(如 fopen()、fwrite() 和 fread() 等函数)并且能够处理文件数据是至关重要的。 在面向对象编程领域,C 语言不像其他一些语言那样含有类和对象的概念。因此,使用 C 进行面向对象编程需要使用结构体和指针等基础语言特性进行代码实现。 使用指针可以实现对象的成员方法,通过通过结构体类型的变量进行指向不同函数的指针赋值,这样就可以构造出“类”的行为,从而实现“对象”的定义和使用。 此外,在基于 C 的面向对象编程中,你还需要掌握以下重要概念: 1. 继承:通过使用结构体包含结构体来实现,可以构造出继承的行为。 2. 封装:使用结构体将某些数据和行为包裹在一起,实现数据的封装。 3. 多态:通过使用指针作为函数参数的形式参数,可以通过多态实现不同的行为。 总之,学习 C 的过程会是一个持续的过程,需要不断地学习和经验积累。而面向对象编程则需要更多的实践和案例的学习,进行不断的探索和尝试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

herb.dr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值