6

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?

Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 1
    As far as I know std::vector does not have capacity constructor. It has size constructor which actually allocates the objects not just reserve a memory – Humam Helfawi Apr 08 '16 at 06:28
  • 14
    For me, throwing an `std::bad_alloc` is the most idiomatic way to report an "out of memory" problem. After all, your constructor can't actually do its job if it cannot get the memory. – Baum mit Augen Apr 08 '16 at 06:29
  • 1
    Remember it's not just `std::vector` which can run out of memory. So can `std::string` or any other class using memory. But most 64 bit systems don't run out of memory. Instead, memory gets swapped to disk, and the program grinds to a virtual halt. – MSalters Apr 08 '16 at 06:43
  • I am aware that this is a universal dynamic allocation issue that can even happen in C malloc, but I really want to delegate the program to crash while running the code belonging to the program containing the structure, rather than inside the structure. C++ exceptions do not give Java-like reminders to catch/rethrow them, so it is not obvious where things can go wrong until they do go wrong. – Dmytro Apr 08 '16 at 06:49
  • 4
    Do what the standard library does. Trust me, many a bold newcomer tried to do better. – n. m. could be an AI Apr 08 '16 at 07:14
  • 1
    Actually nowadays you can't reliably detect memory allocation errors. See this http://stackoverflow.com/questions/32678285/function-doesnt-throw-bad-alloc-exception – user3159253 Apr 08 '16 at 07:22
  • 4
    Constructor exceptions don't "take down the whole program". They can be caught just fine, and you can continue execution. If memory is insufficient, maybe it should "take down the whole program", uncaught. Perhaps invalid input is causing it to request unreasonable quantities of memory that would slow the whole machine, unchecked. – doug65536 Apr 08 '16 at 07:29
  • 2
    I don't see the point entirely. You can just put the constructor in a `try` and `catch` block? And otherwise, if the program cannot be executed normally, do you want to continue? – Chiel Apr 08 '16 at 07:34
  • It's normal for constructors to throw if they cannot construct the object. Many classes do this. The programmer will be aware of this possibility already, you don't need to activate bells and whistles. Your class documentation should describe exactly what exceptions are possible. (Note: move-constructors are a bit of an exception to this rule (no pun intended!), it's common to make the move-constructor `noexcept` as it typically does not need to allocate memory; and container operations are more efficient if they know they can rely on a move-constructor not throwing). – M.M Apr 10 '16 at 03:11
  • 1
    Like @user3159253 says, it's actually worse than you think. In most OS's the allocation will still succeed even if there isn't enough physical RAM available, and then when the program actually tries to read or write to the allocated RAM, the page fault routine will be unable to supply a page of backing store, at which point the program will be killed in order to make the OS usable again. Given that, I wouldn't worry too much about throwing an exception -- if the computer runs out of memory, you're pretty much screwed no matter what. – Jeremy Friesner Apr 10 '16 at 03:15
  • People have tried. Don't bother trying to catch out of memory exceptions as a way of failing gracefully. If your program tries to allocate memory properly and it fails, the entire system is borked. – eoD .J Apr 10 '16 at 05:43

1 Answers1

1

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:

  1. Programmer must provide blocks of memory that are already available. Since memory is already there and you know how much of it you have, you can just not allocate more than you can afford.
  2. The data structure uses its own memory management scheme using a separate process that will crash instead of the process the structure is hosted on.
  3. The data structure is managed by a separate process altogether, similar to database servers. This makes it easy to restart the process in case they fail.
Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 3
    "Throwing exceptions is questionable". If you don't trust your language and its standard library designers, perhaps you've chosen a wrong language. – n. m. could be an AI Apr 10 '16 at 04:03
  • You can throw and catch bad_alloc without allocating any memory. So there is no problem with the throw. – rici Apr 10 '16 at 04:14
  • I'll check if trying the leak test again after checking some compiler flags will let me catch the exception. For now it says "terminate called recursively This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. ." EDIT: g++ leak.cpp -o leak -O3 -s -DNDEBUG didn't change anything. – Dmytro Apr 10 '16 at 04:17
  • @rici I can throw and catch std::bad_alloc in trivial cases, but when running leak test, the program crashes before the exception is handled and the program is terminated from there. – Dmytro Apr 10 '16 at 04:38
  • what is your target platform? If it's a general purpose OS, then don't worry at all. Even catching bad_alloc is completely useless. although it may seem too radical, but it is. – user3159253 Apr 10 '16 at 05:10
  • @user3159253 being able to catch std::bad_alloc isn't too useful, but it gives you more leverage to recover than if you can't. For example, I may be able to introduce a strategy for freeing big chunks of memory under stress, or moving it into a file. It's a question of having options. Although, my tests show that malloc does return 0 on fail so this can be done, new seems to resist attempts of handling it though. – Dmytro Apr 10 '16 at 05:23
  • NOTE: added a nothrow example that works for allocating with new. It seems it's more reliable than exceptions. – Dmytro Apr 10 '16 at 05:31
  • Yeah, in order to guarantee that your program doesn't terminate on OOM, you probably need some runtime configuration dependent on your OS. In particular, you'd need to turn off optimistic vm alloc. Without seeing what your leak tester does, it is hard to comment. – rici Apr 10 '16 at 05:34
  • @rici They leak individual bytes until a new/malloc fails, then it reports how many bytes it leaked. The runtime cleans up after you regardless. The tests are meant to check if you can actually print out how many bytes are leaked at the point of failure and exit out of your program's free will, rather being forcibly terminated by the runtime/operating system. The exception code fails to catch the exception when it reaches its limit, whereas the other two successfully just return 0 on failure and exit gracefully. – Dmytro Apr 10 '16 at 05:37
  • under a decent general purpose memory allocator `new`, `malloc` or even `brk` don't allocate memory at all. See the optimistic malloc examples that I've mentioned earlier in the comments. That is, your `new`/`malloc`/whatever will succeed, but when later your program will to _access_ that memory, it will be killed. Or, maybe, not, if OOM killer will find a better target. So you're trying to design a place that will be hardly useful in "read-life conditions" of a _general-purpose_ application. – user3159253 Apr 10 '16 at 06:17
  • If you target a specific platform without virtualization, without other dirty tricks to get more outcome for less money for hardware, then you should clearly understand how that particular platform works. – user3159253 Apr 10 '16 at 06:19
  • Fair points. I have successfully managed to run the same program allocating what I thought to be the limit multiple times without any issues until I ran 6 copies of these programs (each of them hogged memory until i pressed a key). Perhaps randomized indexing loop over the memory I expect to have would make better tests than just reserving it. – Dmytro Apr 10 '16 at 06:41
  • You could use quantum mechanics to rent memory from a different universe. No there is no solution other than to throw. Preallocating much memory to be released on out of memory only results in one more out-of-memory condition over time -- so there is nothing gained other than unnecessary code and memory usage. –  Apr 11 '16 at 19:16
  • @ExcessPhase I am not sure that you can catch an out of memory exception. If you can, I am not managing to do so. If it's going to break regardless, then assertion will do the same thing without introducing an additional scope and questions to the program. That said I'll see what I can do to prune my post. – Dmytro Apr 11 '16 at 19:20
  • @Dmitry -- yes I'm doing this since 20 years. On 32bit system with 4GB RAM this is very feasible. The same applies to to 64bit systems under special circumstances. I don't want to think about the mess called OutOfMemoryKiller. But even in this case, memory is just one type of fallible resource allocations -- why would you treat memory allocations different than any other resource allocation? –  Apr 11 '16 at 19:23
  • @Dmitry -- people had this discussion 20 years ago and decided for C++ Exception Handling! –  Apr 11 '16 at 19:28
  • @ExcessPhase I understand, but I can't manage to actually catch the std::bad_alloc exception. I can check for nullptr, but once it throws bad_alloc, I can't catch it. That's why I'm curious. – Dmytro Apr 11 '16 at 19:30
  • @dmitry -- Do you have by any chance code compiled with a different compiler on the stack (like C or fortran compiler)? –  Apr 11 '16 at 19:42
  • @ExcessPhase Please rephrase what you are asking. – Dmytro Apr 11 '16 at 19:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/108859/discussion-between-excessphase-and-dmitry). –  Apr 11 '16 at 20:07