new and Exceptions

Started by
15 comments, last by davepermen 17 years, 6 months ago
Say im writing an industrial-strength uptime-critical application (such as a large important server), which has to be as fault-tolerant as possible - Should every instance of 'new' be surrounded by a try-catch block incase I run out of memory? (this app may be running on limited resources) If this is the case, wouldnt it be easier to check for this error condition of bad_alloc's if new just returned NULL, instead of throwing an exception? I've heard about this no_throw typedef that you can pass to new to make it do this, but I've never used it so far. Is the sytax something like "Foo* f = new(no_throw) Foo();"? Also, if this app is also performance-critical, is there going to be a performance advantage from using a "no throwing new" instead of putting exception handlers everywhere?
Allways question authority......unless you're on GameDev.net, then it will hurt your rating very badly so just shut the fuck up.
Advertisement
Wrapping everything in try/catch blocks doesn't solve the problem. The proper solution is to write robust exception-safe code. Unfortunately for you, this is much more difficult than just try/catching everywhere.

A very, very brief summary might be: throw when you need to, catch where you can, and where you have to.

RAII, copy-swap and related idioms can minimize the amount of "work" you need to do in the face of an exception, and make your code more robust and fault-tolerant to boot.

A number of GotW articles on the subject.
The C++ FAQ also covers the area.
Quote:Original post by jpetrie
Wrapping everything in try/catch blocks doesn't solve the problem. The proper solution is to write robust exception-safe code. Unfortunately for you, this is much more difficult than just try/catching everywhere.

A very, very brief summary might be: throw when you need to, catch where you can, and where you have to. Use RAII and related idioms to minimize the amount of "work" you need to do in the face of an exception.

A number of GotW articles on the subject.
The C++ FAQ also covers the area.


Heh, i just finished reading that C++ FAQ before I posted this ;) [edit]thanks for the GotW link tho![/edit]

I'm not wrapping everything in try/catch's... I'm just wondering about memory allocation errors, and the best way to handle them.

At the moment I havnt run into any problems where I need to throw my own custom exceptions (im sure the time is approaching tho), but I know new can throw if it cant allocate the requested memory, and some functions where I am using new need to have the no-throw guarentee.
Allways question authority......unless you're on GameDev.net, then it will hurt your rating very badly so just shut the fuck up.
If a function that allocates memory has to guarantee it can't throw, then you have to suppress the exception. It is still better to suppress it via a catch (instad of nothrow), because you'll likely want to catch any other operations that could throw and try to handle them, if you can (allowing an exception to propagate beyond a function with the throw() specifier will call unexpected() and shoot your app dead).

Oh, and...

Quote:
Also, if this app is also performance-critical, is there going to be a performance advantage from using a "no throwing new" instead of putting exception handlers everywhere?


No, not really. Exception handling code is generated whether or not you use them -- an exception could still pass through your code and appropriate action needs to be taken.
Quote:Original post by PhilMorton
Say im writing an industrial-strength uptime-critical application (such as a large important server), which has to be as fault-tolerant as possible -

Should every instance of 'new' be surrounded by a try-catch block incase I run out of memory? (this app may be running on limited resources)


If by "surrounded" you mean right there (e.g. try { ptr = new T; } catch (std::bad_alloc) { ... }) then no. If you plan on handling allocation failures individually right at that point, you should use the "nothrow" version of new as you mention:

T * ptr = new (std::nothrow) T;if ( !ptr ) {    /* allocation failure */}


Far saner, however, is to use RAII construct (smart pointers, etc) so you can deal with the problem at a higher point in the logic:

void serve_request() {    socket s;    try {         s.open( ... );         boost::scoped_ptr< A > a( new A );         boost::scoped_ptr< B > b( new B );         std::vector< C > c( 1000 );         ...    } catch ( std::bad_alloc ) { //called if ANY of those allocations failed         s << "HTTP/1.1 505\n\n" << ...; //semi-bullshit HTTP headers off the top of my head         s << "Server running low on memory - retry request later";         s.close();    } catch ( const socket_lib_error & e ) {         error_log << "Socket failed: " << e.what() << std::endl;    } catch ( const std::exception & e ) {         error_log << "Unknown exception caught: " << e.what() << std::endl;         page_admin();    }}


Quote:Also, if this app is also performance-critical, is there going to be a performance advantage from using a "no throwing new" instead of putting exception handlers everywhere?


"Depends". Some compilers have a (relatively small) cost for entering and leaving try blocks, so you definately don't want to pepper these throughout the inner loops of your raytracer - such a thing is asking for trouble and usually isn't a good idea anyways. This is part of the reason I used the nothrow version when checking the single allocation and dealing with a failure there immediately. The actual act of throwing the exception (and unwinding the stack) is definately on the "expensive" list too, so you're definately not going to want to use it for (relatively) commonly occuring operations.

That said, returning and checking error codes also has a small performance cost, so using exceptions can actually be a little faster when well placed (at higher levels of logic execution like above, for rarely occuring conditions).
Quote:Original post by MaulingMonkey
If by "surrounded" you mean right there (e.g. try { ptr = new T; } catch (std::bad_alloc) { ... }) then no. If you plan on handling allocation failures individually right at that point, you should use the "nothrow" version of new as you mention:
T * ptr = new (std::nothrow) T;if ( !ptr ) {    /* allocation failure */}
Thanks, yeah thats what I meant. Most of the code that i'm using this error checking in is fairly low-level and handles errors right as they happen.
Quote:Original post by MaulingMonkey
Far saner, however, is to use RAII construct (smart pointers, etc) so you can deal with the problem at a higher point in the logic
Scott Meyers books recently convinced me of how important RAII is, so now im learning about auto_ptr and the boost pointer templates etc :D

Quote:Original post by jpetrie
If a function that allocates memory has to guarantee it can't throw, then you have to suppress the exception. It is still better to suppress it via a catch (instad of nothrow), because you'll likely want to catch any other operations that could throw and try to handle them, if you can (allowing an exception to propagate beyond a function with the throw() specifier will call unexpected() and shoot your app dead).
Thanks. So I guess i should only use the no_throw technique if Im certain that no other important exceptions are going to be supressed by it.
Allways question authority......unless you're on GameDev.net, then it will hurt your rating very badly so just shut the fuck up.
Quote:Original post by PhilMorton
So I guess i should only use the no_throw technique if Im certain that no other important exceptions are going to be supressed by it.


They won't.

Cool factoids based on VS2005 Standard on my local machine:

VS2k5 can throw and catch ~126k exceptions/second on my machine (based on a 1 million exception sampling) in default release mode settings.

VS2k5 was able to warn me (when compiling for release mode - probably due to link time code generation) that the catch statements in my other two exception benchmarking tests that the code would never be reached, even though the actual implementation of the function being called was in a seperate translation unit with no throwing specifiers.

VS2k5 didn't warn me about the equivilant error-based version, oddly.

In release mode, 1 million iterations of the benchmark took "0 seconds" for everything but said throwing function.

In debug mode, the results were:

Benchmark                 |     Errors |   Expected |       Time |Local try/catch + throw   |    1000000 |    1000000 |     11.453 |Local try/catch           |          0 |          0 |      0.078 |Nonlocal try/catch        |          0 |          0 |      0.063 |Error codes               |          0 |          0 |      0.078 |Error codes + errors      |    1000000 |    1000000 |      0.062 |


Note: The timing mechanism used was boost::timer, I'm uncertain how coarsely grained it is.

Switching back to release mode + 1,000,000,000 iterations (disabling the first test) I got:

Benchmark                 |     Errors |   Expected |       Time |Local try/catch + throw   |          0 | 1000000000 |          0 |Local try/catch           |          0 |          0 |      0.593 | # ~1,686,340,640 entries/leaves from try blocks/secondNonlocal try/catch        |          0 |          0 |          0 |Error codes               |          0 |          0 |          0 |Error codes + errors      | 1000000000 | 1000000000 |          0 |


At this point I needed to upgrade to a 64 bit loop in preperation to increase my iterations by another factor of x1000 for tests 3-5. However, even without the x1000 increase, my performance results were altered drastically enough I realized there was no signal to be found in the noise:

Benchmark                 |     Errors |   Expected |       Time |Local try/catch + throw   |          0 | 1000000000 |          0 |Local try/catch           |          0 |          0 |      1.468 |Nonlocal try/catch        |          0 |          0 |        1.5 |Error codes               |          0 |          0 |      1.422 |Error codes + errors      | 1000000000 | 1000000000 |      1.547 |


Benchmarking code used (obviously contrived, and thus flawed to some degree or another - alterations described above involve #if 0 ... #endif-ing out test bodies to disable them, and later, using unsigned __int64 instead of just unsigned in 32 bit mode):

#include <boost/timer.hpp>#include <boost/lexical_cast.hpp>#include <iostream>#include <iomanip>void do_throw();     //other.cpp implementation: { throw std::exception(); }void do_no_throw();  //other.cpp implementation: {}unsigned return_0(); //other.cpp implementation: { return 0; } //"success"unsigned return_1(); //other.cpp implementation: { return 1; } //"error"template < typename BenchT , typename ErrorsT , typename ExpectedT , typename TimeT >void benchmark_display( const BenchT    & benchmark					  , const ErrorsT   & errors					  , const ExpectedT & expected					  , const TimeT     & time					  ){	std::cout <<                    benchmark << " | "	          << std::setw( 10 ) << errors    << " | "			  << std::setw( 10 ) << expected  << " | "			  << std::setw( 10 ) << time      << " | "			  << std::endl;}int main () {	const unsigned loops = 1000000;	boost::timer t;		//--- Benchmark 1 - local try/catch block, exceptions throw	unsigned local_try_catch_block_throwing_caught = 0;	t.restart();	for ( unsigned i = 0 ; i < loops ; ++i ) {		try { do_throw(); }		catch( const std::exception & ) { ++local_try_catch_block_throwing_caught; }	}	double local_try_catch_block_throwing_elapsed = t.elapsed();	//--- Benchmark 2 - local try/catch block, exceptions not thrown	unsigned local_try_catch_block_caught = 0;	t.restart();	for ( unsigned i = 0 ; i < loops ; ++i ) {		try { do_no_throw(); }		catch( const std::exception & ) { ++local_try_catch_block_caught; } //warning C4702: unreachable code	}	double local_try_catch_block_elapsed = t.elapsed();	//--- Benchmark 3 - nonlocal try/catch bloc, exceptions not thrown	unsigned nonlocal_try_catch_block_caught = 0;	t.restart();	try {		for ( unsigned i = 0 ; i < loops ; ++i ) {			do_no_throw();		}	} catch( const std::exception * ) { ++nonlocal_try_catch_block_caught; } //warning C4702: unreachable code	double nonlocal_try_catch_block_elapsed = t.elapsed();	//--- Benchmark 4 - error codes, returning 0 ("success")	unsigned errors_using_return_0 = 0;	t.restart();	for ( unsigned i = 0 ; i < loops ; ++i ) {		if ( return_0() ) { ++errors_using_return_0; }	}	double errors_using_return_0_elapsed = t.elapsed();	//--- Benchmark 5 - error codes, returning 1 ("failure")	unsigned errors_using_return_1 = 0;	t.restart();	for ( unsigned i = 0 ; i < loops ; ++i ) {		if ( return_1() ) { ++errors_using_return_1; }	}	double errors_using_return_1_elapsed = t.elapsed();	benchmark_display( "Benchmark                " , "Errors" , "Expected" , "Time" );	benchmark_display( "Local try/catch + throw  " , local_try_catch_block_throwing_caught , loops , local_try_catch_block_throwing_elapsed );	benchmark_display( "Local try/catch          " , local_try_catch_block_caught          , 0     , local_try_catch_block_elapsed          );	benchmark_display( "Nonlocal try/catch       " , nonlocal_try_catch_block_caught       , 0     , nonlocal_try_catch_block_elapsed       );	benchmark_display( "Error codes              " , errors_using_return_0                 , 0     , errors_using_return_0_elapsed          );	benchmark_display( "Error codes + errors     " , errors_using_return_1                 , loops , errors_using_return_1_elapsed          );}


(The machine in question was running XP Home w/ a 2.4 GHz P4)
If you're using an older version of Visual C++, another thing to be aware of is that new might return NULL instead of throwing.

Quote:Beginning in Visual C++ .NET 2002, the CRT's new function (in libc.lib, libcd.lib, libcmt.lib, libcmtd.lib, msvcrt.lib, and msvcrtd.lib) will continue to return NULL if memory allocation fails. However, the new function in the Standard C++ Library (in libcp.lib, libcpd.lib, libcpmt.lib, libcpmtd.lib, msvcprt.lib, and msvcprtd.lib) will support the behavior specified in the C++ standard, which is to throw a std::bad_alloc exception if the memory allocation fails.

Normally, if you #include one of the C++ standard headers, like <new>, you'll get a /defaultlib directive in your object that will reference the appropriate C++ Standard Library according to the CRT model you used (the /M* compiler options). Generally, that will cause the linker to use the throwing operator new from the C++ Standard Library instead of the nonthrowing one from the main CRT, because the order of defaultlib directives will cause libcp.lib to be searched before libc.lib (under /ML).

Even if you use one of the C++ standard library headers, it is possible to get non-throwing new.


This apparently has been changed in 2005.

Quote:
Checking the result of new is a bug in C++

At least, it is a bug in VC8. That check won't happen. Reading Larry Osterman's recent posts "What's wrong with this code, part 15" and the answers, reminded me this behavior changed in VC8. If you check the result of new in code compiled with VC8, your code is wrong. The call to new will throw. Yes, your code worked in 5.0, 6.0, (maybe) 7.0 and 7.1, but it doesn't now.

You can get the old behavior if you link with nothrownew.obj. The linker will then make every call to new in your dll (or exe) non-throwing. This is incompatible with STL, or any other code that relies on new throwing.


Of course, if you're not using Visual C++ this doesn't apply to you. And i'm not sure if this also applies to 2003.
Quote:Thanks, yeah thats what I meant. Most of the code that i'm using this error checking in is fairly low-level and handles errors right as they happen.


Handled *how*, exactly? What are you planning to *do* if you run out of memory? If it's "if anything at all can't be allocated, then report an error and roll back the global state to where it was before the request", then (a) you may as well do it at a higher level (i.e. try { handleRequestFromUser(args); } - because otherwise you'll be producing huge amounts of code duplication and manually forwarding error conditions back to callers in order to communicate the problem all the way back to the output module - BTW this is exactly the sort of thing the exception mechanism is supposed to do for you automatically if you just leave it alone!); and (b) you will *need* to write exception-safe code (although this will probably be pretty important *anyway*).

Quote:
Thanks. So I guess i should only use the no_throw technique if Im certain that no other important exceptions are going to be supressed by it.


No; you want to use it where no exceptions *at all* are going to be *generated* locally. The no_throw technique (a) doesn't really suppress exceptions (it simply chooses not to emit them when it otherwise would) and (b) can't "suppress" anything besides std::bad_alloc. And any exception *at all* out of a function declared as throw() will blow up.
Quote:Original post by PhilMorton
Say im writing an industrial-strength uptime-critical application (such as a large important server), which has to be as fault-tolerant as possible -


Then maybe you shouldn't use C++...

OK.

Have you thought about a using custom new_handler (which is called on allocation failure)? The default one just raises bad_alloc (yes, that's where the exception ultimately comes from), but you may have something smarter you can do.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan

This topic is closed to new replies.

Advertisement