Coverage Report

Created: 2025-08-26 07:54

/src/PcapPlusPlus/Common++/header/ObjectPool.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#include <stack>
4
#include <mutex>
5
#include <memory>
6
#include <stdexcept>
7
#include <limits>
8
#include <type_traits>
9
10
namespace pcpp
11
{
12
  namespace internal
13
  {
14
    /// @brief A generic object pool implementation.
15
    ///
16
    /// This class provides a generic object pool that can be used to efficiently manage and reuse objects of any
17
    /// type. Objects can be acquired from the pool using the `acquireObject` method, and released back to the pool
18
    /// using the `releaseObject` method. If the pool is empty when acquiring an object, a new object will be
19
    /// created. If the pool is full when releasing an object, the object will be deleted.
20
    ///
21
    /// @tparam T The type of objects managed by the pool. Must be default constructable.
22
    template <class T, std::enable_if_t<std::is_default_constructible<T>::value, bool> = true>
23
    class DynamicObjectPool
24
    {
25
    public:
26
      constexpr static std::size_t DEFAULT_POOL_SIZE = 100;
27
#pragma push_macro("max")  // Undefine max to avoid conflict with std::numeric_limits<std::size_t>::max()
28
#undef max
29
      constexpr static std::size_t INFINITE_POOL_SIZE = std::numeric_limits<std::size_t>::max();
30
#pragma pop_macro("max")
31
32
      /// A constructor for this class that creates a pool of objects
33
      /// @param[in] maxPoolSize The maximum number of objects in the pool
34
      /// @param[in] initialSize The number of objects to preallocate in the pool
35
      explicit DynamicObjectPool(std::size_t maxPoolSize = DEFAULT_POOL_SIZE, std::size_t initialSize = 0)
36
4
          : m_MaxPoolSize(maxPoolSize)
37
4
      {
38
4
        if (initialSize > maxPoolSize)
39
0
        {
40
0
          throw std::invalid_argument("Preallocated objects cannot exceed the maximum pool size");
41
0
        }
42
43
4
        if (initialSize > 0)
44
4
        {
45
4
          this->preallocate(initialSize);
46
4
        }
47
4
      }
48
49
      // These don't strictly need to be deleted, but don't need to be implemented for now either.
50
      DynamicObjectPool(const DynamicObjectPool&) = delete;
51
      DynamicObjectPool(DynamicObjectPool&&) = delete;
52
      DynamicObjectPool& operator=(const DynamicObjectPool&) = delete;
53
      DynamicObjectPool& operator=(DynamicObjectPool&&) = delete;
54
55
      /// A destructor for this class that deletes all objects in the pool
56
      ~DynamicObjectPool()
57
4
      {
58
4
        clear();
59
4
      }
60
61
      /// @brief Acquires a unique pointer to an object from the pool.
62
      ///
63
      /// This method acquires a unique pointer to an object from the pool.
64
      /// If the pool is empty, a new object will be created.
65
      ///
66
      /// @return A unique pointer to an object from the pool.
67
      std::unique_ptr<T> acquireObject()
68
125k
      {
69
125k
        return std::unique_ptr<T>(acquireObjectRaw());
70
125k
      }
71
72
      /// @brief Acquires a raw pointer to an object from the pool.
73
      ///
74
      /// This method acquires a raw pointer to an object from the pool.
75
      /// If the pool is empty, a new object will be created.
76
      ///
77
      /// @return A raw pointer to an object from the pool.
78
      T* acquireObjectRaw()
79
125k
      {
80
125k
        std::unique_lock<std::mutex> lock(m_Mutex);
81
82
125k
        if (m_Pool.empty())
83
0
        {
84
          // We don't need the lock anymore, so release it.
85
0
          lock.unlock();
86
0
          return new T();
87
0
        }
88
89
125k
        T* obj = m_Pool.top();
90
125k
        m_Pool.pop();
91
125k
        return obj;
92
125k
      }
93
94
      /// @brief Releases a unique pointer to an object back to the pool.
95
      ///
96
      /// This method releases a unique pointer to an object back to the pool.
97
      /// If the pool is full, the object will be deleted.
98
      ///
99
      /// @param[in] obj The unique pointer to the object to release.
100
      void releaseObject(std::unique_ptr<T> obj)
101
125k
      {
102
125k
        releaseObjectRaw(obj.release());
103
125k
      }
104
105
      /// @brief Releases a raw pointer to an object back to the pool.
106
      ///
107
      /// This method releases a raw pointer to an object back to the pool.
108
      /// If the pool is full, the object will be deleted.
109
      ///
110
      /// @param[in] obj The raw pointer to the object to release.
111
      void releaseObjectRaw(T* obj)
112
125k
      {
113
125k
        std::unique_lock<std::mutex> lock(m_Mutex);
114
115
125k
        if (m_MaxPoolSize == INFINITE_POOL_SIZE || m_Pool.size() < m_MaxPoolSize)
116
125k
        {
117
125k
          m_Pool.push(obj);
118
125k
        }
119
0
        else
120
0
        {
121
          // We don't need the lock anymore, so release it.
122
0
          lock.unlock();
123
0
          delete obj;
124
0
        }
125
125k
      }
126
127
      /// @brief Gets the current number of objects in the pool.
128
      std::size_t size() const
129
      {
130
        std::lock_guard<std::mutex> lock(m_Mutex);
131
        return m_Pool.size();
132
      }
133
134
      /// @brief Gets the maximum number of objects in the pool.
135
      std::size_t maxSize() const
136
      {
137
        std::lock_guard<std::mutex> lock(m_Mutex);
138
        return m_MaxPoolSize;
139
      }
140
141
      /// @brief Sets the maximum number of objects in the pool.
142
      void setMaxSize(std::size_t maxSize)
143
0
      {
144
0
        std::lock_guard<std::mutex> lock(m_Mutex);
145
0
        m_MaxPoolSize = maxSize;
146
0
147
0
        // If the new max size is less than the current size, we need to remove some objects from the pool.
148
0
        while (m_Pool.size() > m_MaxPoolSize)
149
0
        {
150
0
          delete m_Pool.top();
151
0
          m_Pool.pop();
152
0
        }
153
0
      }
154
155
      /// @brief Pre-allocates up to a minimum number of objects in the pool.
156
      /// @param count The number of objects to pre-allocate.
157
      void preallocate(std::size_t count)
158
4
      {
159
4
        std::unique_lock<std::mutex> lock(m_Mutex);
160
161
4
        if (m_MaxPoolSize < count)
162
0
        {
163
0
          throw std::invalid_argument("Preallocated objects cannot exceed the maximum pool size");
164
0
        }
165
166
        // If the pool is already larger than the requested count, we don't need to do anything.
167
12
        for (std::size_t i = m_Pool.size(); i < count; i++)
168
8
        {
169
8
          m_Pool.push(new T());
170
8
        }
171
4
      }
172
173
      /// @brief Deallocates and releases all objects currently held by the pool.
174
      void clear()
175
4
      {
176
4
        std::unique_lock<std::mutex> lock(m_Mutex);
177
12
        while (!m_Pool.empty())
178
8
        {
179
8
          delete m_Pool.top();
180
8
          m_Pool.pop();
181
8
        }
182
4
      }
183
184
    private:
185
      std::size_t m_MaxPoolSize;   ///< The maximum number of objects in the pool
186
      mutable std::mutex m_Mutex;  ///< Mutex for thread safety
187
      std::stack<T*> m_Pool;       ///< The pool of objects
188
    };
189
  }  // namespace internal
190
}  // namespace pcpp