patterncppMinor
Auto-recycling C++11 polymorphic smart pointers
Viewed 0 times
polymorphicautorecyclingsmartpointers
Problem
I've recently read an interesting blog post by Philipp Zschoche:
it explains how it's possible to avoid unnecessary allocations/deallocations by keeping track of previously allocated memory in a stack.
It's very easy to code a drop-in replacement for smart pointers (such as
When creating a smart pointer, before allocating, it's sufficient to check if there is any unused allocated memory block in the recycler: if there is one, use it, otherwise allocate new memory.
Upon destroying a smart pointer, instead of deallocating, simply call the object's destructor and push the now undefined/unusable memory block on the memory recycler stack, so that other smart pointers of the same type can make use of it.
The memory will be only deallocated at the end of the program (or manually).
```
namespace ssvu
{
namespace Internal
{
template class Recycler
{
private:
std::vector ptrs;
std::allocator alloc;
inline T* pop() noexcept
{
SSVU_ASSERT(!ptrs.empty());
auto result(ptrs.back());
ptrs.pop_back();
return reinterpret_cast(result);
}
public:
inline Recycler() = default;
// Deallocate all memory on destruction
inline ~Recycler() noexcept { for(auto p : ptrs) alloc.deallocate(reinterpret_cast(p), 1); }
// Recycle: call the allocated object's destructor, but do not deallocate memory
// Instead, store the pointer to the allocated memory block for reuse
inline void recycle(TBase* mPtr) noexcept(noexcept(alloc.destroy(mPtr)))
{
SSVU_ASSERT(mPtr != nullptr);
alloc.destroy(mPtr);
ptrs.emplace_back(mPtr);
}
it explains how it's possible to avoid unnecessary allocations/deallocations by keeping track of previously allocated memory in a stack.
It's very easy to code a drop-in replacement for smart pointers (such as
std::unique_ptr and std::shared_ptr) that will make use of the memory recycler. When creating a smart pointer, before allocating, it's sufficient to check if there is any unused allocated memory block in the recycler: if there is one, use it, otherwise allocate new memory.
Upon destroying a smart pointer, instead of deallocating, simply call the object's destructor and push the now undefined/unusable memory block on the memory recycler stack, so that other smart pointers of the same type can make use of it.
The memory will be only deallocated at the end of the program (or manually).
```
namespace ssvu
{
namespace Internal
{
template class Recycler
{
private:
std::vector ptrs;
std::allocator alloc;
inline T* pop() noexcept
{
SSVU_ASSERT(!ptrs.empty());
auto result(ptrs.back());
ptrs.pop_back();
return reinterpret_cast(result);
}
public:
inline Recycler() = default;
// Deallocate all memory on destruction
inline ~Recycler() noexcept { for(auto p : ptrs) alloc.deallocate(reinterpret_cast(p), 1); }
// Recycle: call the allocated object's destructor, but do not deallocate memory
// Instead, store the pointer to the allocated memory block for reuse
inline void recycle(TBase* mPtr) noexcept(noexcept(alloc.destroy(mPtr)))
{
SSVU_ASSERT(mPtr != nullptr);
alloc.destroy(mPtr);
ptrs.emplace_back(mPtr);
}
Solution
My main complaint is the use of the
Would be interested to know how this performs in comparison to you vector based version.
I basically swapped the
Other things I did not like
(but are totally my own personal preference so not that important).
Lining up stuff so it is easy to read and maintain:
I believe that makes it a lot easier to read.
Don't put inline where it is not needed.
The keyword
std::vector in such a low level interface. I think that is totally a waste of resources.Would be interested to know how this performs in comparison to you vector based version.
I basically swapped the
std::vector for a TBase* and built a chain. It makes the assumption that TBase is at least the size of a pointer (but I am sure we can add a static assert to achieve this).namespace ssvu
{
namespace Internal
{
template class Recycler
{
struct Chain
{
Chain* next;
};
private:
Chain* chain;
std::allocator alloc;
T* pop() noexcept
{
SSVU_ASSERT(chain != nullptr);
TBase* result = reinterpret_cast(chain);
chain = chain->next;
return result;
}
public:
Recycler()
: chain(nullptr)
{}
// Deallocate all memory on destruction
~Recycler() noexcept
{
Chain* next;
for(;chain != nullptr; chain = next)
{
next = chain->next;
alloc.deallocate(reinterpret_cast(chain), 1);
}
}
// Recycle: call the allocated object's destructor, but do not deallocate memory
// Instead, store the pointer to the allocated memory block for reuse
void recycle(TBase* mPtr) noexcept(noexcept(alloc.destroy(mPtr)))
{
SSVU_ASSERT(mPtr != nullptr);
alloc.destroy(mPtr);
Chain* newHead = reinterpret_cast(mptr);
newHead->next = chain;
chain = newHead;
}
// Create: either reuse allocated unused memory, or allocate a new memory block
template T* create(TArgs&&... mArgs)
{
auto result(chain == nullptr ? alloc.allocate(1) : pop());
alloc.construct(result, std::forward(mArgs)...);
return result;
}
};
template Recycler& getRecycler() noexcept
{
static Recycler result; return result;
}
}
// Uptr is a typedef for std::unique_ptr
template using UptrRecPoly = Uptr;
template using UptrRec = UptrRecPoly;
template using VecUptrRec = std::vector>;
template inline UptrRecPoly makeUptrRecPoly(TArgs&&... mArgs)
{
return {Internal::getRecycler().create(std::forward(mArgs)...), [](TBase* mPtr)
{
Internal::getRecycler().recycle(mPtr);
}};
}
template inline UptrRec makeUptrRec(TArgs&&... mArgs) { return makeUptrRecPoly(std::forward(mArgs)...); }
}Other things I did not like
(but are totally my own personal preference so not that important).
Lining up stuff so it is easy to read and maintain:
template using UptrRecPoly = Uptr;
template using UptrRec = UptrRecPoly;
template using VecUptrRec = std::vector>;I believe that makes it a lot easier to read.
Don't put inline where it is not needed.
The keyword
inline plays absolutely no role in code inlining. Don't use it for that. It only plays a role in the one definition rule so you need it if a function is defined in a header file that is included into multiple compilation units.Code Snippets
namespace ssvu
{
namespace Internal
{
template<typename T, typename TBase> class Recycler
{
struct Chain
{
Chain* next;
};
private:
Chain* chain;
std::allocator<T> alloc;
T* pop() noexcept
{
SSVU_ASSERT(chain != nullptr);
TBase* result = reinterpret_cast<TBase*>(chain);
chain = chain->next;
return result;
}
public:
Recycler()
: chain(nullptr)
{}
// Deallocate all memory on destruction
~Recycler() noexcept
{
Chain* next;
for(;chain != nullptr; chain = next)
{
next = chain->next;
alloc.deallocate(reinterpret_cast<TBase*>(chain), 1);
}
}
// Recycle: call the allocated object's destructor, but do not deallocate memory
// Instead, store the pointer to the allocated memory block for reuse
void recycle(TBase* mPtr) noexcept(noexcept(alloc.destroy(mPtr)))
{
SSVU_ASSERT(mPtr != nullptr);
alloc.destroy(mPtr);
Chain* newHead = reinterpret_cast<Chain*>(mptr);
newHead->next = chain;
chain = newHead;
}
// Create: either reuse allocated unused memory, or allocate a new memory block
template<typename... TArgs> T* create(TArgs&&... mArgs)
{
auto result(chain == nullptr ? alloc.allocate(1) : pop());
alloc.construct(result, std::forward<TArgs>(mArgs)...);
return result;
}
};
template<typename T, typename TBase> Recycler<T, TBase>& getRecycler() noexcept
{
static Recycler<T, TBase> result; return result;
}
}
// Uptr<T, TDeleter> is a typedef for std::unique_ptr<T, TDeleter>
template<typename T, typename TBase> using UptrRecPoly = Uptr<T, void(*)(TBase*)>;
template<typename T> using UptrRec = UptrRecPoly<T, T>;
template<typename TBase> using VecUptrRec = std::vector<UptrRec<TBase>>;
template<typename T, typename TBase, typename... TArgs> inline UptrRecPoly<T, TBase> makeUptrRecPoly(TArgs&&... mArgs)
{
return {Internal::getRecycler<T, TBase>().create(std::forward<TArgs>(mArgs)...), [](TBase* mPtr)
{
Internal::getRecycler<T, TBase>().recycle(mPtr);
}};
}
template<typename T, typename... TArgs> inline UptrRec<T> makeUptrRec(TArgs&&... mArgs) { return makeUptrRecPoltemplate<typename T, typename TBase> using UptrRecPoly = Uptr<T, void(*)(TBase*)>;
template<typename T> using UptrRec = UptrRecPoly<T, T>;
template<typename TBase> using VecUptrRec = std::vector<UptrRec<TBase>>;Context
StackExchange Code Review Q#52433, answer score: 2
Revisions (0)
No revisions yet.