)
As you can see, it's pretty simple. You just allocate a number of different-sized blocks from the C and C++ heap(s) at the start of processing, and release them in the handler for the out-of-memory handler in the hope that you've given back enough that the cleanup code's got better chances than it would otherwise have of performing an orderly shutdown.
The use of different sized blocks is to try and cover the cases where the heap implementation maintains separate pools for small and/or medium and/or large block sizes.
Note that the heterogeneous use of the STLSoft auto_array_destructor
and scoped_handle
resource wrapper templates leaves something to be desired for neatness and clarity. I've toyed with the idea of writing
a parachutes class (with some wiz-bang expression templates!) to do it, but have never
yet found the driving motivation. That may depend on the responses to this blog entry. ;)
int main_(int argc, char **argv)
{
// Allocate the parachutes
::stlsoft::auto_array_destructor<char> parachute1(new char[10]);
::stlsoft::auto_array_destructor<char> parachute2(new char[100]);
::stlsoft::auto_array_destructor<char> parachute3(new char[1000]);
::stlsoft::auto_array_destructor<char> parachute4(new char[10000]);
::stlsoft::auto_array_destructor<char> parachute5(new char[100000]);
::stlsoft::scoped_handle<void*> parachute6(::malloc(10), ::free);
::stlsoft::scoped_handle<void*> parachute7(::malloc(100), ::free);
::stlsoft::scoped_handle<void*> parachute8(::malloc(1000), ::free);
::stlsoft::scoped_handle<void*> parachute9(::malloc(10000), ::free);
::stlsoft::scoped_handle<void*> parachute10(::malloc(100000), ::free);
try
{
// . . . all the business of the application . . .
}
catch(blog_exception &x)
{
cout << "You've blogged too much recently, big mouth! Details: " << x.what() << endl;
}
catch(another_exception &x)
{
cout << "Nasty things have occured! Details: " << x.what() << endl;
}
catch(::std::bad_alloc &)
{
// Deploy the parachutes with fingers crossed that it leaves enough memory
// for the
delete [] parachute1.detach();
delete [] parachute2.detach();
delete [] parachute3.detach();
delete [] parachute4.detach();
delete [] parachute5.detach();
::free(parachute6.detach());
::free(parachute7.detach());
::free(parachute8.detach());
::free(parachute9.detach());
::free(parachute10.detach());
// . . . all the actions you want to carry out to (attempt to) effect orderly shutdown . . .
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
// The 'generic' main - carries out a minimum of things, minimising risk
// of failing to be in a position to
try
{
return main_(argc, argv);
}
catch(::std::exception &x)
{
fprintf(stderr, "Unhandled error: %s\n", x.what());
}
catch(...)
{
fprintf(stderr, "Unhandled unknown error\n");
}
return EXIT_FAILURE;
}
The technique is so blindingly obvious that I struggle to accept that I've come up with it independently, but I cannot find any examples of its use in this way. One can google for "memory parachute" but that yields discussions of reserves for main-line use, whereas this is solely for helping, but not guaranteeing, orderly shutdown behaviour.
Enlightenment sought, and sorely needed.