Object Pool

Learning Objectives

Implement a bounded object pool that resets objects on return.

Handle exhaustion and concurrency.

Know when pooling hurts.

Prerequisites: Modules 13, 27.

Intuition and Definition

Intent: reuse objects that are expensive to create (connections, large buffers, threads) instead of creating and destroying them repeatedly.

Participants: Pool (owns idle objects, hands them out, takes them back), PooledObject (resettable), and a handle that returns the object automatically (RAII).

Object Pool is not one of the 23 GoF patterns, but it is widely used.

Why It Matters

Database connections take milliseconds to open; 4 MB frame buffers stress the allocator. Pools turn these costs into a one-time start-up cost and give a hard upper bound on resource use.

Syntax and Language Notes

Most platforms ship pools: database connection pools (HikariCP, ADO.NET pooling), .NET ArrayPool<T>.Shared, thread pools everywhere. Write your own only after measuring.

Complete Example

#include <iostream>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <vector>

class Connection {
    int id_;
    std::string session_;
public:
    explicit Connection(int id) : id_(id) { std::cout << "  (expensive) open connection " << id_ << "\n"; }
    void use(const std::string& who) { session_ = who; std::cout << "  conn " << id_ << " serving " << who << "\n"; }
    void reset() { session_.clear(); }                 // clean state before reuse
    int id() const { return id_; }
};

class ConnectionPool {
    std::vector<std::unique_ptr<Connection>> idle_;
    std::mutex m_;
    int created_ = 0;
    const int max_;
public:
    explicit ConnectionPool(int max) : max_(max) {}

    class Lease {                                      // RAII handle: returns the object automatically
        ConnectionPool* pool_;
        std::unique_ptr<Connection> c_;
    public:
        Lease(ConnectionPool* p, std::unique_ptr<Connection> c) : pool_(p), c_(std::move(c)) {}
        Lease(Lease&&) noexcept = default;
        ~Lease() { if (c_) pool_->giveBack(std::move(c_)); }
        Connection* operator->() { return c_.get(); }
    };

    std::optional<Lease> acq
…
(expensive) open connection 1
  (expensive) open connection 2
  conn 1 serving request 1
  conn 2 serving request 2
third acquire: pool exhausted
  conn 1 serving request 3
  conn 1 serving request 4
  conn 1 serving request 5

Step-by-Step Walkthrough

The first two acquisitions create connections; the third finds the pool at its limit and gets nullopt.

When the leases go out of scope, their destructors reset the connections and return them.

Later requests reuse existing connections — no more "open" lines.

The mutex makes acquire/giveBack safe from several threads; a real pool would also let callers *wait* with a timeout (condition variable).

Common Mistakes

Forgetting to reset state — the next user sees the previous user's session, credentials or buffer contents (a security bug).

Leaking leases (no RAII), so the pool slowly drains.

Pooling cheap objects — modern allocators are fast; pools add contention and hold memory.

Unbounded pools — no protection against overload.

Trade-offs and Alternatives

Pools improve throughput for expensive resources and cap resource usage, but add complexity, can hide bugs (stale state), and keep memory resident. Arena/monotonic allocators (std::pmr::monotonic_buffer_resource) are an alternative for many short-lived allocations. Measure first (Module 70).

Real-World Use

Database and HTTP connection pools, thread pools, GPU buffer pools, network packet buffer pools in embedded stacks (lwIP pbufs), ArrayPool<T> in .NET.

Practice

Q1. (Easy) Why should a pool be bounded?

Answer: To cap resource usage and fail or queue predictably under overload.

Q2. (Medium) What does the RAII lease protect against?

Answer: Forgetting to return objects (leaks) — including on exceptions and early returns.

Q3. (Advanced) Name a security risk of pooling and its mitigation.

Answer: Data from one user leaking to the next (buffers, auth state). Reset/zero objects on return (or before reuse) and test that reset is complete.

Interview Question

Q1. "When does an object pool make performance worse?" Answer: When objects are cheap to create (allocation is fast), when the pool becomes a contention point (single mutex under many threads), when pooled objects keep large amounts of memory alive, or when reset is as expensive as construction. Pools also hurt cache locality if objects are scattered. Use profiling to justify them, and prefer per-thread pools or lock-free free lists where contention matters.

Quick Quiz

Q1. Is Object Pool a GoF pattern?

Answer: No.

Q2. What must happen when an object returns to the pool?

Answer: Its state must be reset.

Q3. What should acquire() do when the pool is empty and at capacity?

Answer: Wait (with timeout) or fail fast with a clear error, depending on policy.

Summary

Pools reuse expensive objects, bound resource use and need reset + RAII return.

Measure before pooling; beware contention and stale state.

Related: Modules 13, 60, 70.