3

Before reading this question, I had never taken exception handling seriously. Now I see the necessity but still feel "Writing exception safe code is very hard".

See this example in that question's accepted answer:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

As the answer says, I can easily offer the basic guarantee by using std::unique_ptr. However when I catch a std::bad_alloc, I don't know whether it happens in the push_back or x->doSomethingThatCanThrow(), so I can't know whether t.list is still "good" or its last element is not fully prepared. Then the only choice is to discard t, show a scary message and abort, if t is essential for the entire program.

Code with strong guarantee doesn't have the problem, but “it can become costly” (this example involves a copy of the large list), and not so readable.

A possible solution may be making new wait until memory is available, removing the most annoying exception std::bad_alloc. Then 2. and 3. won't throw (provided X's construction and copy always succeed). I can just wrap 4. in a try block and deal with exceptions here (and pop_back the list). Then the function will provide nothrow guarantee, and the list will always contain good things.

The users won't care the difference between 100% CPU and 100% RAM. When they see a program hangs, they will close other programs so new finds enough memory and continues.

My question: Can this be implemented? Is there a nothrow new that waits until memory is available? Can I apply it globally (e.g. by #define new ...) so libraries before C++ was standardized can survive a temporary 100% RAM?

Community
  • 1
  • 1
jingyu9575
  • 521
  • 3
  • 16
  • 2
    Sanity Check: What if it tries to `new` 100 TB of memory when you only have 4 GB? Do you wait forever? – Mysticial Dec 03 '13 at 06:25
  • "A possible solution may be making new wait until memory is available" — won't work in a single-threaded scenario. And also, `std::bad_alloc` and `100% RAM usage` are absolutely unrelated. – Joker_vD Dec 03 '13 at 06:26
  • 2
    What the? When a user sees your program hang, they don't close other programs. They close yours. – chris Dec 03 '13 at 06:26
  • @Mysticial: then the program is not compatible with the hardware or x86. Waiting forever and letting the user do the force close has no difference from showing "your machine is too old" and forcing close itself. – jingyu9575 Dec 03 '13 at 06:29
  • @chris it's the user's choice. If you find your Dota starting to lag do you close the game or other services? I often open many other programs when copying large files, and if the copy process hangs, I will rather stop reading webpages than have the file half-copy and try to fix it later. – jingyu9575 Dec 03 '13 at 06:38
  • @Joker_vD in a single-threaded scenario I can use the default new. I wonder if there is a way to let my program wait a bit time instead of aborting the operation when I suddenly open a lot of webpages. – jingyu9575 Dec 03 '13 at 06:42
  • @jingyu9575 The `std::bad_alloc` exception is caused by exhaustion of your program's address space, not physical memory limits. You can allocate `new std::vector(1024 * 1024 * 1024, 'x')` on a machine with 256 MB of RAM, and it won't throw, and will work just fine (if slow). – Joker_vD Dec 03 '13 at 06:48
  • @Joker_vD Why? If I have no swap files on disks, where are the 'x's stored? – jingyu9575 Dec 03 '13 at 06:50
  • @Joker_vD `auto* p = new std::vector((std::vector::size_type)(10.0 * 1024 * 1024 * 1024), 'x');` throws `std::bad_alloc` when I turn off virtual memory in Windows 8.1 with 4GB physical memory. If I set virtual memory to "auto", you're right it works but is slow (100% Disk IO). x64 build. – jingyu9575 Dec 03 '13 at 09:47

1 Answers1

2

It's a questionable design, but you can certainly do this with a 'new-handler'. The default new-handler simply throws std::bad_alloc. If the new-handler returns, new will loop, and attempt to allocate again. It is also used by the nothrow new operator, but a std::bad_alloc thrown by the new-handler is caught, and NULL returned, in that case.

You simply need to set the new-handler to your custom void (*)() handler function. At the very least, you might want to put the process to sleep for a while - say, 1/10 sec. Again, it might not be possible for the program to continue anyway - Linux, for example, has the 'OOM killer' which can be configured by an admin.

Brett Hale
  • 21,653
  • 2
  • 61
  • 90
  • IIRC, on Linux `std::bad_alloc` is almost never thrown due to the "overcommiting" feature. – Joker_vD Dec 03 '13 at 13:36
  • any pointers on how to retry the allocation? I'm currently at : `void outOfMemHandler() { std::cerr << "Out of memory, I'm just going to sleep for a while."< 100 //std::abort(); }` – Stefan Rogin Oct 18 '14 at 01:01