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::optional
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?
When new is used to allocate memory for a C++ class object, the object's constructor is called after the memory is allocated. Use the delete operator to deallocate the memory allocated by the new operator.
When allocation of memory is done dynamically using dynamic memory allocator new in a constructor, it is known as dynamic constructor. By using this, we can dynamically initialize the objects.
There are two types of memory allocations. Static and dynamic.
It is automatically called when object goes out of scope. Destructor release memory space occupied by the objects created by constructor. In destructor, objects are destroyed in the reverse of an object creation.
Based on the feedback provided, I am convinced that there is no elegant way to prevent classes that manage dynamic memory from taking down your program without introducing complexity.
Throwing exceptions is questionable because once your class has no memory it can allocate, it may not be able to handle std::bad_alloc that a user can catch.
From more thinking, I come to a realization that one way to prevent the program from crashing from modules it uses, is to move the parts of the program that use these modules to another process, and have that process share memory with the main process, so that if the other process somehow goes down, you can just restart that process and make another request.
As long as you use any classes capable of failing under extreme cases, it is impossible to prevent them from failing unless you are able to control their extreme cases.
In the case of dynamic memory, it is difficult to control exactly how dynamic memory your process has access to, what the allocation policy it has, and how much memory in total has been used by your application. It is thus difficult to control it without being more rigorous about the pools of contiguous memory you use, or moving the data structure to be managed by another process which can be restarted on failure.
Answer: It is inherently impossible to both provide a simple interface/implementation pair of a class that manages dynamic memory. You either have a simple interface/implementation that will take down your program such as std::vector; or a complex interface/implementation that requires one of the following or something more clever:
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