I have a program that contains a processing phase that needs to use a bunch of different object instances (all allocated on the heap) from a tree of polymorphic types, all eventually derived from a common base class.
As the instances may cyclically reference each other, and do not have a clear owner, I want allocated them with new
, handle them with raw pointers, and leave them in memory for the phase (even if they become unreferenced), and then after the phase of the program that uses these instances, I want to delete them all at once.
How I thought to structure it is as follows:
struct B; // common base class vector<unique_ptr<B>> memory_pool; struct B { B() { memory_pool.emplace_back(this); } virtual ~B() {} }; struct D : B { ... } int main() { ... // phase begins D* p = new D(...); ... // phase ends memory_pool.clear(); // all B instances are deleted, and pointers invalidated ... }
Apart from being careful that all B instances are allocated with new, and that noone uses any pointers to them after the memory pool is cleared, are there problems with this implementation?
Specifically I am concerned about the fact that the this
pointer is used to construct a std::unique_ptr
in the base class constructor, before the derived class constructor has completed. Does this result in undefined behaviour? If so is there a workaround?
In case you haven't already, familiarize yourself with Boost.Pool. From the Boost documentation:
What is Pool?
Pool allocation is a memory allocation scheme that is very fast, but limited in its usage. For more information on pool allocation (also called simple segregated storage, see concepts concepts and Simple Segregated Storage.
Why should I use Pool?
Using Pools gives you more control over how memory is used in your program. For example, you could have a situation where you want to allocate a bunch of small objects at one point, and then reach a point in your program where none of them are needed any more. Using pool interfaces, you can choose to run their destructors or just drop them off into oblivion; the pool interface will guarantee that there are no system memory leaks.
When should I use Pool?
Pools are generally used when there is a lot of allocation and deallocation of small objects. Another common usage is the situation above, where many objects may be dropped out of memory.
In general, use Pools when you need a more efficient way to do unusual memory control.
Which pool allocator should I use?
pool_allocator
is a more general-purpose solution, geared towards efficiently servicing requests for any number of contiguous chunks.
fast_pool_allocator
is also a general-purpose solution but is geared towards efficiently servicing requests for one chunk at a time; it will work for contiguous chunks, but not as well aspool_allocator
.If you are seriously concerned about performance, use
fast_pool_allocator
when dealing with containers such asstd::list
, and usepool_allocator
when dealing with containers such asstd::vector
.
Memory management is tricky business (threading, caching, alignment, fragmentation, etc. etc.) For serious production code, well-designed and carefully optimized libraries are the way to go, unless your profiler demonstrates a bottleneck.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With