Можно ли создать пул объектов для shared_ptr?
Набросав это в своей голове, я вижу два способа сделать это, но у каждого есть недостаток:
Если бы объекты T хранились в пуле многократного использования, то действие оборачивания T в shared_ptr по запросу get () привело бы к тому, что каждый раз блок управления перераспределялся в куче, что нарушало концепцию пула объектов.
Если объекты shared_ptr были сохранены в пуле многократного использования, объект shared_ptr должен прекратить существование, чтобы инициировать пользовательское удаление, а пользовательская функция удаления вызывается только с указателем T. Так что нечего утилизировать.
После исчерпывающих исследований и испытаний я пришел к выводу, что есть нет законный способ (начиная с C ++ 11 или ниже) сделать пул объектов многоразового использования shared_ptr<T>
это напрямую. Конечно, можно сделать пул T
объекты довольно легко, что служит shared_ptr<T>
да, но это приводит к выделению кучи с каждой подачей для блока управления.
Это является Однако возможно создать пул объектов shared_ptr<T>
косвенно (а это только как я нашел, чтобы сделать это). Косвенным образом я имею в виду, что нужно реализовать собственный распределитель стилей «пул памяти» для хранения для повторного использования памяти, освобожденной shared_ptr<T>
блоки управления уничтожены. Этот распределитель затем используется в качестве третьего параметра конструктора shared_ptr:
template< class Y, class Deleter, class Alloc >
std::shared_ptr( Y* ptr, Deleter d, Alloc alloc );
shared_ptr<T>
будет по-прежнему создаваться / выделяться и удаляться / выделяться из памяти кучи — остановить ее невозможно — но, сделав память многократно используемой через пользовательский распределитель, можно добиться детерминированного использования памяти.
Да, это возможно. Но вместо того, чтобы вернуть ваш бассейн std::shared_ptr<T>
Я бы подумал вернуть boost::intrusive_ptr<T>
. Вы могли бы иметь intrusive_ptr_release()
быть ответственным за освобождение этого блока из пула, и тогда только ваши пользователи должны создать T
такой, что вы можете сделать intrusive_ptr<T>
,
Вы можете хранить объекты в пуле (например, как unique_ptr). Пул возвращает shared_ptr по запросу. Пользовательский удалитель возвращает данные в пул. Простой пример этого приведен здесь:
#ifndef __POOL_H_
#define __POOL_H_
#include <list>
#include <mutex>
#include <algorithm>
#include <memory>
#include <stdexcept>
namespace common {
template<class T, bool grow_on_demand=true>
class Pool
{
public:
Pool(const char* name_p, size_t n)
: mutex_m(), free_m(0), used_m(0), name_m(name_p)
{
for (size_t i=0; i<n; i++)
{
free_m.push_front( std::make_unique<T>() );
}
}
const char* getName() const
{
return name_m.c_str();
}
std::shared_ptr<T> alloc()
{
std::unique_lock<std::mutex> lock(mutex_m);
if (free_m.empty() )
{
if constexpr (grow_on_demand)
{
free_m.push_front( std::make_unique<T>() );
}
else
{
throw std::bad_alloc();
}
}
auto it = free_m.begin();
std::shared_ptr<T> sptr( it->get(), [=](T* ptr){ this->free(ptr); } );
used_m.push_front(std::move(*it));
free_m.erase(it);
return sptr;
}size_t getFreeCount()
{
std::unique_lock<std::mutex> lock(mutex_m);
return free_m.size();
}
private:
void free(T *obj)
{
std::unique_lock<std::mutex> lock(mutex_m);
auto it = std::find_if(used_m.begin(), used_m.end(), [&](std::unique_ptr<T> &p){ return p.get()==obj; } );
if (it != used_m.end())
{
free_m.push_back(std::move(*it));
used_m.erase(it);
}
else
{
throw std::runtime_error("unexpected: unknown object freed.");
}
}
std::mutex mutex_m;
std::list<std::unique_ptr<T>> free_m;
std::list<std::unique_ptr<T>> used_m;
std::string name_m;
};
}
#endif /* __POOL_H_ */
По умолчанию пул добавляет новые элементы, если вы выделяете новый объект из пустого пула (grow_on_demand = true).
n
элементы и добавляет их в пул (используя конструктор по умолчанию).mypool.alloc()
Вы можете получить объект из бассейна.[=](T* ptr){ this->free(ptr); }
изнутри alloc()
,