Allocations and Exceptions

One of the things I dislike about many programming languages are exceptions. They go against the natural flow of thinking for most programmers. We tend to think of code as flowing in one direction only: forward. But with exceptions, code can flow in two directions. When conditions are normal, code continues to flow forward. But when conditions are exceptional, code starts to flow backwards as the stack unwinds. Much like with multithreaded code, this makes code safety considerably more difficult. One of the exception-based gotchas I see over and over has to do with allocations, which is what I’d like to cover today.

Many programmers will write code that looks something like this:

Foo *f = new Foo( 12 );
Bar *b = new Bar( f );

Make a new Foo, and pass it in to Bar’s constructor — life can’t get any better than this! Then the programmer sits down for a code review with a more senior programmer, and if luck is on their side, the question is raised, “what happens when there’s low memory and you can’t make a new Foo instance?”

So then the code morphs into this:

Foo *f = new Foo( 12 );
if (f) {
	Bar *b = new Bar( f );
	if (!b) {
		delete f;
	} else {
	
	}
}

Make a new Foo instance, and if that works, make a new Bar instance. If that doesn’t work, clean up the Foo instance. Everything’s great now! Except C++ doesn’t work that way by default, and many programmers don’t even know it. C++’s operator new throws exceptions in low memory situations. So what will actually happen in a low memory situation where a new instance Bar cannot be created is that the call to new will throw a std::bad_alloc exception, which won’t be caught. The stack will unwind, leaking the Foo instance that was created. Oops!

So now the programmer has a choice. Either they can wrap all calls to the new operator with a try/catch block, or they can use the std::nothrow form of new. When using std::nothrow, allocation problems will cause the new operator to return NULL instead of throwing a bad_alloc exception. Modifying the previous code would look like this:

Foo *f = new (std::nothrow) Foo( 12 );
if (f) {
	Bar *b = new (std::nothrow) Bar( f );
	if (!b) {
		delete f;
	} else {
	
	}
}

Now the code behaves in the way the programmer expects. If a new instance of Bar cannot be allocated, the operator new returns NULL, and so the Foo instance can also be freed properly. No exceptions are thrown.

So what happens if Bar’s constructor throws an exception? For instance:

Bar::Bar( Foo *f )
{
	if (!f)	{
		throw std::runtime_error( "No Foo passed in to Bar's constructor" );
	}
	if (!f->IsValid()) {
		throw std::runtime_error( "Foo isn't valid in Bar's constructor" );
	}
}

When calling new (std::nothrow) Bar( NULL ), will an exception actually be thrown?

This is where I start to gripe about the naming of std::nothrow, because nothrow is open to interpretation. Creating a new instance of a class happens in multiple stages. First, memory is allocated for the class instance. Then the initializers are fired for the class hierarchy. Then the class constructors are fired for the class hierarchy. Allocation is the job of the new operator, calling the initialization and constructor is the job of the compiler. Keeping this in mind, std::nothrow makes a lot of sense — operator new will be called to allocate memory, and the allocation phase will not throw an exception. However, once operator new has finished allocating memory, the fact that the programmer passed std::nothrow to it is lost. The initializers are fired, and then the constructor is called. That will throw the exception!

So to answer the previous question: yes, the std::runtime_error exception will be thrown, even though std::nothrow was specified when calling new. So don’t let std::nothrow fool you — it doesn’t mean that all exceptions will be caught and turned into returning NULL. It simply means that allocation errors will be caught and turned into returning NULL.

tl;dr: Low memory conditions will throw exceptions by default when calling new. Use std::nothrow to have new return NULL instead of throwing a bad_alloc exception. But beware that exceptions thrown from the constructors will still be fired.

This entry was posted in C/C++ and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.