I am designing an std::vector
-like class for self teaching purposes, but I have come upon a difficult issue of memory allocations inside constructors.
The std::vector
's capacity constructor is very convenient, but comes at a cost of a potential to throw a std::bad_alloc
exception, and take down your whole program with it.
I am struggling to decide what would be the most elegant way to deal with the unlikely scenario of the capacity constructor failing, or best notify the user that by using the constructor, they are consenting to the data structure being able to take down the whole program through an exception.
My first thought was to add a compile-time warning whenever the constructor is called, reminding that the constructor can fail, and that they should make sure to either handle it, or be aware of the risks involved with using the constructor.
This solution seemed bad because it, if applied on global scale, would cause too many warnings, and make a bad impression.
My second idea was to make the constructor private, and require the constructor to be reached through a static "requestConstruct"-like method, similar to Singleton pattern.
This solution makes the interface look odd.
I had a few more ideas, but all of them seem to damage the "feel" of the interface. These ideas are:
- boost-like
boost::optional
- Haskell-like maybes
- forcing the user to give me a pool of memory that the structure is authorized to. This idea seems very elegant but I can't find a clean/popular static memory pool implementation for C/C++. I did successfully make a test at https://github.com/CodeDmitry/MemoryPools though.
- use descriptive assertions apologizing for blowing up the world, and explain what happened in detail. Which is what I am doing at the moment.
- try to allocate a few more times before calling it quits and crash the whole program. This seems like a good strategy, but feels more like crossing fingers, as the program can still crash due to behavior of the structure(rather than the program). This approach may just be paranoid and wrong since a failed allocation won't necessarily give you the chance to "re-allocate", and will just shut down the program.
- invent some sort of stress-test mechanism to give confidence to the owner of the structure that it can handle the most capacity the user expects (which can be hard because these stress-tests can be very misleading, as memory can be available now, but under more memory intensive times, it may not).
There is also a funny possibility of not having enough memory left to actually catch the exception. This program seems to not catch it(Which may or may not be related to having enough memory).
#include <stdint.h>
#include <exception>
#include <iostream>
#include <stdlib.h>
int main(int argc, char **argv)
{
uint_fast32_t leaked_bytes = 0;
for (;;) {
try {
new uint8_t;
} catch (const std::bad_alloc& e) {
std::cout << "successfully leaked" << leaked_bytes << " bytes." << '\n';
exit(0);
} catch (const std::exception& e) {
std::cout << "I caught an exception, but not sure what it was...\n";
exit(0);
}
++leaked_bytes;
}
}
This program does let me handle the failure before the program is terminated though:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
int main(int argc, char **argv)
{
uint_fast32_t bytes_leaked = 0;
for (;;) {
if (malloc(1) == 0) {
printf("leaked %u bytes.\n", bytes_leaked);
exit(0);
}
++bytes_leaked;
}
return 0;
}
nothrow works correctly as well:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <new>
int main(int argc, char **argv)
{
uint64_t leaked_bytes = 0;
for (;;) {
uint8_t *byte = new (std::nothrow) uint8_t;
if (byte == nullptr) {
printf("leaked %llu bytes.\n", leaked_bytes);
exit(0);
}
++leaked_bytes;
}
return 0;
}
EDIT:
I think I found a solution to this issue. There is inherent naivety in storing dynamic data structures on the main process. That is, these structures are not guaranteed to succeed, and may break at any point.
It is better to do creation of all dynamic structures in another process, and have some sort of restart policy in the event that it has an unexpected issue.
This does impose overhead for communicating to the data structure, but it stops the main program from having to manage everything that can go wrong with the data structure, which can quickly take over majority of the code in main.
end EDIT.
Is there a proper way to handle allocation issues in such dynamic classes, increase awareness of my user about these risks?