Memory allocation inside constructors?

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?

+5
source share
1 answer

Based on the feedback received, I am convinced that there is no elegant way to prevent classes that manage dynamic memory from removing your program without introducing complexity.

Throwing exceptions is questionable, because as soon as your class does not have memory that it can allocate, it may not be able to handle std :: bad_alloc, which the user can catch.

From a lot of thought, I came to understand that one of the ways to prevent a program from crashing from the modules that it uses is to move parts of the program that use these modules to another process, and this process exchanges memory with the main process, so if another process somehow goes down, you can just restart that process and make another request.

While you are using any classes that can fail in extreme cases, it is impossible to prevent them from crashing if you cannot control your extreme cases.

In the case of dynamic memory, it is difficult to determine exactly which dynamic memory your process has access to, what is the distribution policy, and how much memory was used by your application. Thus, it is difficult to control it without being more restrictive regarding the pools of contiguous memory that you are using, or moving a data structure that will be controlled by another process that might restart if it fails.

The answer . In essence, it is not possible to provide a simple interface / implementation pair of a class that manages dynamic memory. You either have a simple interface / implementation that will lower your program, for example std :: vector; or a complex interface / implementation that requires one of the following or something smarter:

  • The programmer must provide blocks of memory that are already available. Since memory already exists, and you know how much you have, you can simply not allocate more than you can afford.
  • The data structure uses its own memory management scheme, using a separate process that will be split, rather than the process in which the structure is located.
  • The data structure is controlled by a separate process as a whole, like database servers. This makes it easy to restart the process if they do not work.
0
source

Source: https://habr.com/ru/post/1246676/


All Articles