I am developing a std::vector -like class for self-learning purposes, but I ran into the difficult problem of allocating memory inside constructors.
The capacity constructor std::vector very convenient, but it costs due to the ability to throw an exception std::bad_alloc and remove your entire program from it.
I'm struggling to decide what would be the most elegant way to deal with the unlikely scenario of a bandwidth constructor failure, or better notify the user that, using the constructor, they agree that the entire program with the exception can demolish the data structure.
My first thought was to add a compilation warning whenever a constructor is called, recalling that the constructor may fail, and that it must be sure to handle it or be aware of the risks associated with using the constructor.
This decision seemed bad, because if applied globally, it 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 the static requestConstruct method, similar to the Singleton pattern.
This solution makes the interface odd.
I got a few more ideas, but they all seem to hurt the “feel” of the interface. These ideas:
- boost-like
boost::optional - Haskell-like maybes
- forcing the user to provide me with a memory pool to which the structure is allowed. This idea seems very elegant, but I cannot find a clean / popular static memory pool implementation for C / C ++. I successfully passed the test at https://github.com/dmitrymakhnin/MemoryPools .
- use descriptive statements apologizing for bloating the world and explain what happened in detail. This is what I am doing at the moment .
- try highlighting a few more times before calling it shutting down and crashing the entire program. This sounds like a good strategy, but more like crossing fingers, because the program can still crash due to the behavior of the structure (not the program). This approach may just be paranoid and wrong, because a failed distribution will not necessarily give you the ability to “redistribute” and just shut down the program.
- invent some kind of stress test mechanism to give confidence to the owner of the structure that he can cope with the maximum ability that the user expects (which can be difficult, because these stress tests can be very deceiving, because memory may be available now, but in a more intense memory time, this may not be.)
There is also the fun possibility of not having enough memory to actually catch the exception. This program does not seem to catch it (which may or may not be due to the presence of sufficient memory).
#include <stdint.h> #include <exception> #include <iostream> #include <stdlib.h> int main(int argc, char **argv) { uint_fast32_t leaked_bytes = 0; while (1) { 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 allows me to handle the failure before the program is completed, though:
#include <stdlib.h> #include <stdint.h> #include <stdio.h> int main(int argc, char **argv) { uint_fast32_t bytes_leaked = 0; while (1) { if (malloc(1) == 0) { printf("leaked %u bytes.\n", bytes_leaked); exit(0); } ++bytes_leaked; } return 0; }
nothrow works correctly:
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <new> int main(int argc, char **argv) { uint64_t leaked_bytes = 0; while (1) { 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 problem. There is an inherent naivety in storing dynamic data structures in the main process. That is, these structures do not guarantee success and can break at any time.
It is best to create all dynamic structures in another process and have some restart strategy in case of an unexpected problem.
This imposes an overhead on communication with the data structure, but does not allow the main program to manage everything that can go wrong than with the data structure, which can quickly take on most of the main code.
end EDIT.
Is there a way to deal with distribution problems in such dynamic classes, to increase my user’s awareness of these risks?