I would like to take advantage of the following advertised feature of boost::fast_pool_allocator
(see the Boost documentation for Boost Pool):
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...
(See here for this quote.)
The key phrase is drop them off into oblivion. I do not want the destructors called on these objects.
(The reason is that I have millions of tiny objects that form an extremely complex web of ownership on the heap, and it takes my program about 20 minutes to call all of the destructors when the single parent-most object goes off the stack. I do not need these destructors called, because there are no desired side effects and all memory is contained within the boost::pool
.)
Unfortunately, despite the promise of the above documentation, and the promise of the boost::pool
concept, I cannot find a way to prevent the destructors of the managed objects from being called.
The problem is easily isolated in a small sample program:
class Obj
{
public:
~Obj()
{
// Placing a breakpoint here indicates that this is *always* reached
// (except for the crash scenario discussed below)
int m = 0;
}
};
typedef std::map<int, Obj, std::less<int>,
boost::fast_pool_allocator<std::pair<int const, Obj>>>
fast_int_to_int_map;
class Foo
{
public:
~Foo()
{
// When the following line is uncommented, the program CRASHES
// when the destructor is exited - because the Obj destructors
// are called on the invalid Obj ghost instances
//boost::singleton_pool<boost::fast_pool_allocator_tag,
// sizeof(std::pair<int const, Obj>)>::purge_memory();
}
fast_int_to_int_map mmap;
};
void mfoo()
{
// When this function exits, the Foo instance goes off the stack
// and its destructor is called, in turn calling the destructors
// of the Obj instances - this is NOT desired!
Foo foo;
foo.mmap[0] = Obj();
foo.mmap[1] = Obj();
}
int main()
{
mfoo();
// The following line deallocates the memory of the pool just fine -
// but does nothing to prevent the destructors of the Obj instances
// from being called
boost::singleton_pool<boost::fast_pool_allocator_tag,
sizeof(std::pair<int const, Obj>)>::purge_memory();
}
As noted in the code comments, the destructors of the Obj
instances which are managed by the boost::pool
are always called.
What can I do to make the promising quote from the Boost Pool documention, drop them off into oblivion
, come true?
You pass the custom allocator into your std::map
class. So, this allocator will be used for everything inside of std::map
: for all Obj
data and also for nodes of a binary tree of std::map
. As the result, if you call purge_memory()
in Foo
's destructor then all this memory becomes invalid, and it crashes in std::map
destructor.
Your assumption that a custom allocator is responsible for objects' de-allocation is not correct: it's a std::map
's task to free all objects. So, ~Obj()
will be called regardless if you pass the custom allocator or if you use a default one.
I don't see any elegant solution for your question. But this approach should work:
- use
placement new
to create Foo
object from pool's memory,
- use this
Foo
object as usual,
- call
purge_memory()
to release all memory from the pool. No destructors ~Obj
or ~std::map
will be called.
By default pool uses the default_user_allocator_new_delete
allocator. This will destroy underlying objects by calling the destructor first and then reclaiming underlying memory. The default_user_allocator_malloc_free
will cause malloc'ed memory to be reclaimed without firing the destructor - hence drop[ing] them off into oblivion
.
That said, if your tree is really that complicated, using free instead of firing destructors seems like a really good way to start chopping branches out from under yourself and potentially start leaking memory you can no longer reach.
The answer to this question is contained in the comments beneath @qehgt's answer, and in detail in this new question.
Namely:
There is a clear and formal distinction between two related aspects of object instantiation and deletion:
The purpose of a custom allocator is (1) only.
The container objects handle (2), and they call functions in the custom allocator to determine the location of memory in which to construct the object, and to tell the custom allocator that it's OK to free the allocated memory for given objects. But the calls to the constructor/destructor themselves are made by the container, not by the custom allocator.
Therefore, the way to achieve the goal of this question is the following: Declare the container object via new
and NEVER CALL delete
on it (but use the custom allocator to guarantee that the objects you later create in the container are stored in the memory pool, and then manually free the memory pool yourself by calling purge_memory()
):
class Obj { // has operator<() for use with std::set };
typedef std::set<Obj, std::less<Obj>, boost::fast_pool_allocator<Obj>> fast_set_obj;
// Deliberately do not use a managed pointer -
// I will *NOT* delete this object, but instead
// I will manage the memory using the memory pool!!!
fast_set_obj * mset = new fast_set_obj;
// ... add some Obj's to 'mset'
mset->insert(Obj());
mset->insert(Obj());
// Do something desireable with the set ...
...
// All done.
// It's time to release the memory, but do NOT call any Obj destructors.
// The following line of code works exactly as intended.
boost::singleton_pool<boost::fast_pool_allocator_tag, sizeof(Obj const)>::purge_memory();
(The above code is taken from the linked question, above.)
However, I am still having an issue, not directly related to the intention behind this current question: The code above does not work if a map
is used instead of a set
. See the linked question for details.