Coverage Report

Created: 2025-07-11 06:47

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