design patterns – writing a generic object pool in C++

For a hobby project, My aim is to design and implement a generic system for pooling an arbitrary class of object. I tried implementing it using templates.
This pool should support 4 actions:
“Create a pool”
“Allocate an object from this pool”
“Deallocate an object from this pool”
“Destroy this pool”
For sake of simplicity, there is no multi-threading involved.

  1. If I have to pool classes like this example, how do I do that?
typedef char            ByteType;
typedef void*           PointerType;
typedef char            FixedStringType(256);

// A basic struct
struct Point
{
    int x, y, z;
};
  1. Can someone please provide your thoughts/feedback on bugs and how to improve this code?
//ObjectPool.h
#pragma once
#include <vector>
#include <iostream>

const int g_MaxNumberOfObjectsInPool = 2;

template<typename T>
class DefaultAllocator {
public:
    T* operator()() {
        return new T{};
    }
    void operator()(T* p) {
        delete p;
    }
    void reset() {
        std::cout << "reset function called from default allocator" << std::endl;
    }
};

template<typename T, typename AllocatorT=DefaultAllocator<T>>
class ObjectPool
{
    struct ObjectInfo {
        bool isInUse{};
        T* ptrObject{};
    };
    static inline std::vector<ObjectInfo> poolObjects{};
    static inline AllocatorT allocator{};
public:
    static T* getObject() {
        for (auto& currObj : poolObjects) {
            if (!currObj.isInUse) {
                currObj.isInUse = true;
                std::cout << "Existing Object returned" << std::endl;
                return currObj.ptrObject;
            }
        }

        if (poolObjects.size() == g_MaxNumberOfObjectsInPool) {
            std::cout << "Pool is full " << std::endl;
            return nullptr;
        }

        std::cout << "Creating a new object" << std::endl;
        //auto newObj = new T{};
        auto newObj = allocator();
        poolObjects.push_back({ true, newObj });        
        return newObj;
    }

    static void releaseObject(T* ptrSo) {
        for (auto& currObj : poolObjects) {
            if (currObj.ptrObject == ptrSo) {
                currObj.isInUse = false;
                return;
            }
        }
    }

    static void destroy() {
        for (auto& currObj : poolObjects) {
            if (currObj.isInUse) {
                std::cout << "WARNING! this object is still in use" << std::endl;
            }
            allocator(currObj.ptrObject);
        }
        allocator.reset();
        poolObjects.clear();
    }
};

The main file looks like this:

#include "ObjectPool.h"
#include <iostream>

class PrivateClass {
    PrivateClass() {

    }
public:
    void func() {

    }
    friend class PrivateAllocator;
};

class PrivateAllocator {
public:
    PrivateClass* operator()() {
        return new PrivateClass{};
    }
    void operator()(PrivateClass* p) {
        delete p;
    }
    void reset() {
        std::cout << "reset function called" << std::endl;
    }
};

int main() {
    using integer = ObjectPool<int>;
    auto int1 = integer::getObject();
    auto int2 = integer::getObject();
    auto int3 = integer::getObject();

    integer::releaseObject(int1);
    auto int4 = integer::getObject();
    integer::destroy();
    return 0;
}