If the model is that you keep tight control of the resources all the time, and only let the client gain access for some time period, you might do something like this:
#include <memory> // std::unique_ptr
#include <vector> // std::vector
#include <utility> // std::swap, std::move
#include <cassert> // assert
template<typename T> class Pool;
// represent reference to an object in pool
template<typename T> class PooledObjectRef {
public:
// empty
PooledObjectRef() {}
// move
PooledObjectRef(PooledObjectRef&& other)
: PooledObjectRef() {
swap(other);
}
PooledObjectRef& operator = (PooledObjectRef&& other) {
swap(other);
return *this;
}
// cannot copy
PooledObjectRef(const PooledObjectRef& other) = delete;
PooledObjectRef& operator = (const PooledObjectRef& other) = delete;
// has resource or not
operator bool() const {
return resource_ != nullptr;
}
// access the real resouce
T& operator*() {
return *resource_;
}
const T& operator*() const {
return *resource_;
}
// return resource to pool
void recycle();
// automatically recycle
~PooledObjectRef() {
recycle();
}
public:
void swap(PooledObjectRef& other) {
std::swap(pool_, other.pool_);
std::swap(resource_, other.resource_);
}
private:
friend class Pool<T>;
PooledObjectRef(Pool<T>* pool, T* resource)
: pool_(pool), resource_(resource) {
}
void wipe() {
pool_ = nullptr;
resource_ = nullptr;
}
private:
Pool<T>* pool_ = nullptr;
T* resource_ = nullptr;
};
// manage a pool of resources
template<typename T> class Pool {
public:
template<typename... ARGS>
Pool(size_t n, ARGS&&... args) {
for(size_t i = 0; i < n; ++i) {
free_.push_back(std::make_unique<T>(std::forward<ARGS...>(args)...));
}
}
PooledObjectRef<T> acquire() {
if( not free_.empty() ) {
used_.push_back( std::move(free_.back()) );
free_.pop_back();
return PooledObjectRef<T>{this, used_.back().get()};
}
return {};
}
bool recycle(PooledObjectRef<T>& obj) {
if( obj.pool_ == this ) {
auto it = std::ranges::find_if(used_, [&obj](auto& p) {
return obj.resource_ = p.get();
});
if( it != free_.end() ) {
free_.push_back( std::move(*it) );
used_.erase(it);
obj.wipe();
return true;
}
else {
assert(false); // coding error
}
}
else {
assert(false); // pass in object from different pool.
}
return false;
}
size_t available() const {
return free_.size();
}
bool empty() const {
return available() == 0u;
}
private:
std::vector<std::unique_ptr<T>> free_;
std::vector<std::unique_ptr<T>> used_;
};
template<typename T>
inline void PooledObjectRef<T>::recycle() {
if( pool_ ) {
[[maybe_unused]] bool ok = pool_->recycle(*this);
assert(ok); // should never fail
}
}
int main() {
Pool<int> pool(2, 42);
assert(pool.available() == 2);
{
auto ptr = pool.acquire();
assert(ptr && *ptr == 42);
assert(pool.available() == 1);
}
assert(pool.available() == 2);
{
auto ptr = pool.acquire();
assert(ptr && *ptr == 42);
assert(pool.available() == 1);
ptr.recycle();
assert(not ptr);
assert(pool.available() == 2);
}
assert(pool.available() == 2);
{
auto ptr = pool.acquire();
assert(ptr && *ptr == 42);
assert(pool.available() == 1);
pool.recycle(ptr);
assert(not ptr);
assert(pool.available() == 2);
}
assert(pool.available() == 2);
{
auto ptr1 = pool.acquire();
assert(ptr1);
auto ptr2 = pool.acquire();
assert(ptr2);
assert(pool.empty());
auto ptr3 = pool.acquire();
assert(not ptr3);
}
return 0;
}